mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-24 20:48:55 +00:00
Add riscv arch crate
Basic support for riscv. This, combined with follow on commits adds basic support for booting a linux kernel on a riscv machine. This has been tested with a qemu host as the riscv hypervisor extension is not yet widely available in hardware. Change-Id: I44f83f1acf1be2297b62d1f10311e3e47319e5f8 Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/4460936 Commit-Queue: Daniel Verkamp <dverkamp@chromium.org> Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
parent
495f4913f7
commit
f30889ca70
3 changed files with 884 additions and 0 deletions
25
riscv64/Cargo.toml
Normal file
25
riscv64/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "riscv64"
|
||||
version = "0.1.0"
|
||||
authors = ["Rivos Inc."]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
arch = { path = "../arch" }
|
||||
cros_fdt = { path = "../cros_fdt" }
|
||||
data_model = { path = "../common/data_model" }
|
||||
devices = { path = "../devices" }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
kernel_cmdline = { path = "../kernel_cmdline" }
|
||||
kvm = { path = "../kvm" }
|
||||
kvm_sys = { path = "../kvm_sys" }
|
||||
libc = "*"
|
||||
minijail = "*"
|
||||
rand = "0.8"
|
||||
remain = "*"
|
||||
resources = { path = "../resources" }
|
||||
sync = { path = "../common/sync" }
|
||||
thiserror = "*"
|
||||
base = { path = "../base" }
|
||||
vm_control = { path = "../vm_control" }
|
||||
vm_memory = { path = "../vm_memory" }
|
355
riscv64/src/fdt.rs
Normal file
355
riscv64/src/fdt.rs
Normal file
|
@ -0,0 +1,355 @@
|
|||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use cros_fdt::Error;
|
||||
use cros_fdt::FdtWriter;
|
||||
use cros_fdt::Result;
|
||||
use devices::irqchip::aia_aplic_addr;
|
||||
use devices::irqchip::aia_imsic_size;
|
||||
use devices::irqchip::AIA_APLIC_SIZE;
|
||||
use devices::irqchip::AIA_IMSIC_BASE;
|
||||
use devices::PciAddress;
|
||||
use devices::PciInterruptPin;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::RngCore;
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_memory::GuestMemory;
|
||||
|
||||
// This is the start of DRAM in the physical address space.
|
||||
use crate::RISCV64_PHYS_MEM_START;
|
||||
|
||||
// CPUs are assigned phandles starting with this number.
|
||||
const PHANDLE_CPU0: u32 = 0x100;
|
||||
|
||||
const PHANDLE_AIA_APLIC: u32 = 2;
|
||||
const PHANDLE_AIA_IMSIC: u32 = 3;
|
||||
const PHANDLE_CPU_INTC_BASE: u32 = 4;
|
||||
|
||||
fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemory) -> Result<()> {
|
||||
let mut mem_reg_prop = Vec::new();
|
||||
let mut previous_memory_region_end = None;
|
||||
let mut regions = guest_mem.guest_memory_regions();
|
||||
regions.sort();
|
||||
for region in regions {
|
||||
// Merge with the previous region if possible.
|
||||
if let Some(previous_end) = previous_memory_region_end {
|
||||
if region.0 == previous_end {
|
||||
*mem_reg_prop.last_mut().unwrap() += region.1 as u64;
|
||||
previous_memory_region_end =
|
||||
Some(previous_end.checked_add(region.1 as u64).unwrap());
|
||||
continue;
|
||||
}
|
||||
assert!(region.0 > previous_end, "Memory regions overlap");
|
||||
}
|
||||
|
||||
mem_reg_prop.push(region.0.offset());
|
||||
mem_reg_prop.push(region.1 as u64);
|
||||
previous_memory_region_end = Some(region.0.checked_add(region.1 as u64).unwrap());
|
||||
}
|
||||
|
||||
let memory_node = fdt.begin_node("memory")?;
|
||||
fdt.property_string("device_type", "memory")?;
|
||||
fdt.property_array_u64("reg", &mem_reg_prop)?;
|
||||
fdt.end_node(memory_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32, timebase_frequency: u32) -> Result<()> {
|
||||
let cpus_node = fdt.begin_node("cpus")?;
|
||||
fdt.property_u32("#address-cells", 0x1)?;
|
||||
fdt.property_u32("#size-cells", 0x0)?;
|
||||
fdt.property_u32("timebase-frequency", timebase_frequency)?;
|
||||
|
||||
for cpu_id in 0..num_cpus {
|
||||
let cpu_name = format!("cpu@{:x}", cpu_id);
|
||||
let cpu_node = fdt.begin_node(&cpu_name)?;
|
||||
fdt.property_string("device_type", "cpu")?;
|
||||
fdt.property_string("compatible", "riscv")?;
|
||||
fdt.property_string("mmu-type", "sv48")?;
|
||||
fdt.property_string("riscv,isa", "rv64iafdcsu_smaia_ssaia")?;
|
||||
fdt.property_string("status", "okay")?;
|
||||
fdt.property_u32("reg", cpu_id)?;
|
||||
fdt.property_u32("phandle", PHANDLE_CPU0 + cpu_id)?;
|
||||
|
||||
// Add interrupt controller node
|
||||
let intc_node = fdt.begin_node("interrupt-controller")?;
|
||||
fdt.property_string("compatible", "riscv,cpu-intc")?;
|
||||
fdt.property_u32("#interrupt-cells", 1)?;
|
||||
fdt.property_null("interrupt-controller")?;
|
||||
fdt.property_u32("phandle", PHANDLE_CPU_INTC_BASE + cpu_id)?;
|
||||
fdt.end_node(intc_node)?;
|
||||
|
||||
fdt.end_node(cpu_node)?;
|
||||
}
|
||||
|
||||
fdt.end_node(cpus_node)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_chosen_node(
|
||||
fdt: &mut FdtWriter,
|
||||
cmdline: &str,
|
||||
initrd: Option<(GuestAddress, usize)>,
|
||||
) -> Result<()> {
|
||||
let chosen_node = fdt.begin_node("chosen")?;
|
||||
fdt.property_u32("linux,pci-probe-only", 1)?;
|
||||
fdt.property_string("bootargs", cmdline)?;
|
||||
|
||||
let mut kaslr_seed_bytes = [0u8; 8];
|
||||
OsRng.fill_bytes(&mut kaslr_seed_bytes);
|
||||
let kaslr_seed = u64::from_le_bytes(kaslr_seed_bytes);
|
||||
fdt.property_u64("kaslr-seed", kaslr_seed)?;
|
||||
|
||||
let mut rng_seed_bytes = [0u8; 256];
|
||||
OsRng.fill_bytes(&mut rng_seed_bytes);
|
||||
fdt.property("rng-seed", &rng_seed_bytes)?;
|
||||
|
||||
if let Some((initrd_addr, initrd_size)) = initrd {
|
||||
let initrd_start = initrd_addr.offset() as u64;
|
||||
let initrd_end = initrd_start + initrd_size as u64;
|
||||
fdt.property_u64("linux,initrd-start", initrd_start)?;
|
||||
fdt.property_u64("linux,initrd-end", initrd_end)?;
|
||||
}
|
||||
|
||||
fdt.end_node(chosen_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// num_ids: number of imsic ids from the aia subsystem
|
||||
// num_sources: number of aplic sources from the aia subsystem
|
||||
fn create_aia_node(
|
||||
fdt: &mut FdtWriter,
|
||||
num_cpus: usize,
|
||||
num_ids: usize,
|
||||
num_sources: usize,
|
||||
) -> Result<()> {
|
||||
let name = format!("imsics@{:#08x}", AIA_IMSIC_BASE);
|
||||
let imsic_node = fdt.begin_node(&name)?;
|
||||
fdt.property_string("compatible", "riscv,imsics")?;
|
||||
|
||||
let regs = [
|
||||
0u32,
|
||||
AIA_IMSIC_BASE as u32,
|
||||
0,
|
||||
aia_imsic_size(num_cpus) as u32,
|
||||
];
|
||||
fdt.property_array_u32("reg", ®s)?;
|
||||
fdt.property_u32("#interrupt-cells", 0)?;
|
||||
fdt.property_null("interrupt-controller")?;
|
||||
fdt.property_null("msi-controller")?;
|
||||
fdt.property_u32("riscv,num-ids", num_ids as u32)?;
|
||||
fdt.property_u32("phandle", PHANDLE_AIA_IMSIC)?;
|
||||
|
||||
const S_MODE_EXT_IRQ: u32 = 9;
|
||||
let mut cpu_intc_regs: Vec<u32> = Vec::with_capacity(num_cpus * 2);
|
||||
for hart in 0..num_cpus {
|
||||
cpu_intc_regs.push(PHANDLE_CPU_INTC_BASE + hart as u32);
|
||||
cpu_intc_regs.push(S_MODE_EXT_IRQ);
|
||||
}
|
||||
fdt.property_array_u32("interrupts-extended", &cpu_intc_regs)?;
|
||||
|
||||
fdt.end_node(imsic_node)?;
|
||||
|
||||
/* Skip APLIC node if we have no interrupt sources */
|
||||
if num_sources > 0 {
|
||||
let name = format!("aplic@{:#08x}", aia_aplic_addr(num_cpus));
|
||||
let aplic_node = fdt.begin_node(&name)?;
|
||||
fdt.property_string("compatible", "riscv,aplic")?;
|
||||
|
||||
let regs = [
|
||||
0u32,
|
||||
aia_aplic_addr(num_cpus) as u32,
|
||||
0,
|
||||
AIA_APLIC_SIZE as u32,
|
||||
];
|
||||
fdt.property_array_u32("reg", ®s)?;
|
||||
fdt.property_u32("#interrupt-cells", 2)?;
|
||||
fdt.property_null("interrupt-controller")?;
|
||||
fdt.property_u32("riscv,num-sources", num_sources as u32)?;
|
||||
fdt.property_u32("phandle", PHANDLE_AIA_APLIC)?;
|
||||
fdt.property_u32("msi-parent", PHANDLE_AIA_IMSIC)?;
|
||||
fdt.end_node(aplic_node)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// PCI host controller address range.
|
||||
///
|
||||
/// This represents a single entry in the "ranges" property for a PCI host controller.
|
||||
///
|
||||
/// See [PCI Bus Binding to Open Firmware](https://www.openfirmware.info/data/docs/bus.pci.pdf)
|
||||
/// and https://www.kernel.org/doc/Documentation/devicetree/bindings/pci/host-generic-pci.txt
|
||||
/// for more information.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct PciRange {
|
||||
pub space: PciAddressSpace,
|
||||
pub bus_address: u64,
|
||||
pub cpu_physical_address: u64,
|
||||
pub size: u64,
|
||||
pub prefetchable: bool,
|
||||
}
|
||||
|
||||
/// PCI address space.
|
||||
#[derive(Copy, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub enum PciAddressSpace {
|
||||
/// PCI configuration space
|
||||
Configuration = 0b00,
|
||||
/// I/O space
|
||||
Io = 0b01,
|
||||
/// 32-bit memory space
|
||||
Memory = 0b10,
|
||||
/// 64-bit memory space
|
||||
Memory64 = 0b11,
|
||||
}
|
||||
|
||||
/// Location of memory-mapped PCI configuration space.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct PciConfigRegion {
|
||||
/// Physical address of the base of the memory-mapped PCI configuration region.
|
||||
pub base: u64,
|
||||
/// Size of the PCI configuration region in bytes.
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
fn create_pci_nodes(
|
||||
fdt: &mut FdtWriter,
|
||||
pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>,
|
||||
cfg: PciConfigRegion,
|
||||
ranges: &[PciRange],
|
||||
) -> Result<()> {
|
||||
// Add devicetree nodes describing a PCI generic host controller.
|
||||
// See Documentation/devicetree/bindings/pci/host-generic-pci.txt in the kernel
|
||||
// and "PCI Bus Binding to IEEE Std 1275-1994".
|
||||
let ranges: Vec<u32> = ranges
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let ss = r.space as u32;
|
||||
let p = r.prefetchable as u32;
|
||||
[
|
||||
// BUS_ADDRESS(3) encoded as defined in OF PCI Bus Binding
|
||||
(ss << 24) | (p << 30),
|
||||
(r.bus_address >> 32) as u32,
|
||||
r.bus_address as u32,
|
||||
// CPU_PHYSICAL(2)
|
||||
(r.cpu_physical_address >> 32) as u32,
|
||||
r.cpu_physical_address as u32,
|
||||
// SIZE(2)
|
||||
(r.size >> 32) as u32,
|
||||
r.size as u32,
|
||||
]
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
let bus_range = [0, 0]; // Only bus 0
|
||||
let reg = [cfg.base, cfg.size];
|
||||
|
||||
const IRQ_TYPE_LEVEL_HIGH: u32 = 0x00000004;
|
||||
let mut interrupts: Vec<u32> = Vec::new();
|
||||
let mut masks: Vec<u32> = Vec::new();
|
||||
|
||||
for (address, irq_num, irq_pin) in pci_irqs.iter() {
|
||||
// PCI_DEVICE(3)
|
||||
interrupts.push(address.to_config_address(0, 8));
|
||||
interrupts.push(0);
|
||||
interrupts.push(0);
|
||||
|
||||
// INT#(1)
|
||||
interrupts.push(irq_pin.to_mask() + 1);
|
||||
|
||||
// INTERRUPT INFO
|
||||
interrupts.push(PHANDLE_AIA_APLIC);
|
||||
interrupts.push(*irq_num);
|
||||
interrupts.push(IRQ_TYPE_LEVEL_HIGH);
|
||||
|
||||
// PCI_DEVICE(3)
|
||||
masks.push(0xf800); // bits 11..15 (device)
|
||||
masks.push(0);
|
||||
masks.push(0);
|
||||
|
||||
// INT#(1)
|
||||
masks.push(0x7); // allow INTA#-INTD# (1 | 2 | 3 | 4)
|
||||
}
|
||||
|
||||
let pci_node = fdt.begin_node("pci")?;
|
||||
fdt.property_string("compatible", "pci-host-cam-generic")?;
|
||||
fdt.property_string("device_type", "pci")?;
|
||||
fdt.property_array_u32("ranges", &ranges)?;
|
||||
fdt.property_array_u32("bus-range", &bus_range)?;
|
||||
fdt.property_u32("#address-cells", 3)?;
|
||||
fdt.property_u32("#size-cells", 2)?;
|
||||
fdt.property_array_u64("reg", ®)?;
|
||||
fdt.property_u32("#interrupt-cells", 1)?;
|
||||
fdt.property_array_u32("interrupt-map", &interrupts)?;
|
||||
fdt.property_array_u32("interrupt-map-mask", &masks)?;
|
||||
fdt.property_u32("msi-parent", PHANDLE_AIA_IMSIC)?;
|
||||
fdt.property_null("dma-coherent")?;
|
||||
fdt.end_node(pci_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a flattened device tree containing all of the parameters for the
|
||||
/// kernel and loads it into the guest memory at the specified offset.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `fdt_max_size` - The amount of space reserved for the device tree
|
||||
/// * `guest_mem` - The guest memory object
|
||||
/// * `pci_irqs` - List of PCI device address to PCI interrupt number and pin mappings
|
||||
/// * `pci_cfg` - Location of the memory-mapped PCI configuration space.
|
||||
/// * `pci_ranges` - Memory ranges accessible via the PCI host controller.
|
||||
/// * `num_cpus` - Number of virtual CPUs the guest will have
|
||||
/// * `fdt_load_offset` - The offset into physical memory for the device tree
|
||||
/// * `cmdline` - The kernel commandline
|
||||
/// * `initrd` - An optional tuple of initrd guest physical address and size
|
||||
/// * `timebase_frequency` - The time base frequency for the VM.
|
||||
pub fn create_fdt(
|
||||
fdt_max_size: usize,
|
||||
guest_mem: &GuestMemory,
|
||||
pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>,
|
||||
pci_cfg: PciConfigRegion,
|
||||
pci_ranges: &[PciRange],
|
||||
num_cpus: u32,
|
||||
fdt_load_offset: u64,
|
||||
aia_num_ids: usize,
|
||||
aia_num_sources: usize,
|
||||
cmdline: &str,
|
||||
initrd: Option<(GuestAddress, usize)>,
|
||||
timebase_frequency: u32,
|
||||
) -> Result<()> {
|
||||
let mut fdt = FdtWriter::new(&[]);
|
||||
|
||||
// The whole thing is put into one giant node with some top level properties
|
||||
let root_node = fdt.begin_node("")?;
|
||||
fdt.property_string("compatible", "linux,dummy-virt")?;
|
||||
fdt.property_u32("#address-cells", 0x2)?;
|
||||
fdt.property_u32("#size-cells", 0x2)?;
|
||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
||||
create_memory_node(&mut fdt, guest_mem)?;
|
||||
create_cpu_nodes(&mut fdt, num_cpus, timebase_frequency)?;
|
||||
create_aia_node(&mut fdt, num_cpus as usize, aia_num_ids, aia_num_sources)?;
|
||||
create_pci_nodes(&mut fdt, pci_irqs, pci_cfg, pci_ranges)?;
|
||||
|
||||
// End giant node
|
||||
fdt.end_node(root_node)?;
|
||||
|
||||
let fdt_final = fdt.finish()?;
|
||||
if fdt_final.len() > fdt_max_size {
|
||||
return Err(Error::TotalSizeTooLarge);
|
||||
}
|
||||
|
||||
let fdt_address = GuestAddress(RISCV64_PHYS_MEM_START + fdt_load_offset);
|
||||
let written = guest_mem
|
||||
.write_at_addr(fdt_final.as_slice(), fdt_address)
|
||||
.map_err(|_| Error::FdtGuestMemoryWriteError)?;
|
||||
if written < fdt_final.len() {
|
||||
return Err(Error::FdtGuestMemoryWriteError);
|
||||
}
|
||||
Ok(())
|
||||
}
|
504
riscv64/src/lib.rs
Normal file
504
riscv64/src/lib.rs
Normal file
|
@ -0,0 +1,504 @@
|
|||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! RISC-V 64-bit architecture support.
|
||||
|
||||
#![cfg(target_arch = "riscv64")]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::{self};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use arch::get_serial_cmdline;
|
||||
use arch::GetSerialCmdlineError;
|
||||
use arch::MsrConfig;
|
||||
use arch::MsrExitHandlerError;
|
||||
use arch::RunnableLinuxVm;
|
||||
use arch::VmComponents;
|
||||
use arch::VmImage;
|
||||
use base::Event;
|
||||
use base::SendTube;
|
||||
use devices::serial_device::SerialHardware;
|
||||
use devices::serial_device::SerialParameters;
|
||||
use devices::Bus;
|
||||
use devices::BusDeviceObj;
|
||||
use devices::BusError;
|
||||
use devices::IrqChipRiscv64;
|
||||
use devices::PciAddress;
|
||||
use devices::PciConfigMmio;
|
||||
use devices::PciDevice;
|
||||
use devices::PciRootCommand;
|
||||
use hypervisor::CoreRegister;
|
||||
use hypervisor::CpuConfigRiscv64;
|
||||
use hypervisor::Hypervisor;
|
||||
use hypervisor::ProtectionType;
|
||||
use hypervisor::TimerRegister;
|
||||
use hypervisor::VcpuInitRiscv64;
|
||||
use hypervisor::VcpuRegister;
|
||||
use hypervisor::VcpuRiscv64;
|
||||
use hypervisor::Vm;
|
||||
use hypervisor::VmRiscv64;
|
||||
#[cfg(windows)]
|
||||
use jail::FakeMinijailStub as Minijail;
|
||||
#[cfg(unix)]
|
||||
use minijail::Minijail;
|
||||
use remain::sorted;
|
||||
use resources::AddressRange;
|
||||
use resources::SystemAllocator;
|
||||
use resources::SystemAllocatorConfig;
|
||||
use sync::Mutex;
|
||||
use thiserror::Error;
|
||||
use vm_control::BatteryType;
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_memory::MemoryRegionOptions;
|
||||
|
||||
mod fdt;
|
||||
|
||||
// We place the kernel at offset 8MB
|
||||
const RISCV64_KERNEL_OFFSET: u64 = 0x20_0000;
|
||||
const RISCV64_INITRD_ALIGN: u64 = 8;
|
||||
const RISCV64_FDT_ALIGN: u64 = 0x40_0000;
|
||||
|
||||
// This indicates the start of DRAM inside the physical address space.
|
||||
const RISCV64_PHYS_MEM_START: u64 = 0x8000_0000;
|
||||
|
||||
// PCI MMIO configuration region base address.
|
||||
const RISCV64_PCI_CFG_BASE: u64 = 0x1_0000;
|
||||
// PCI MMIO configuration region size.
|
||||
const RISCV64_PCI_CFG_SIZE: u64 = 0x100_0000;
|
||||
// This is the base address of MMIO devices.
|
||||
const RISCV64_MMIO_BASE: u64 = 0x0300_0000;
|
||||
// Size of the whole MMIO region.
|
||||
const RISCV64_MMIO_SIZE: u64 = 0x10_0000;
|
||||
|
||||
const RISCV64_FDT_MAX_SIZE: u64 = 0x1_0000;
|
||||
|
||||
fn get_kernel_addr() -> GuestAddress {
|
||||
GuestAddress(RISCV64_PHYS_MEM_START + RISCV64_KERNEL_OFFSET)
|
||||
}
|
||||
|
||||
const RISCV64_IRQ_BASE: u32 = 1;
|
||||
|
||||
#[sorted]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("unable to clone an Event: {0}")]
|
||||
CloneEvent(base::Error),
|
||||
#[error("failed to clone IRQ chip: {0}")]
|
||||
CloneIrqChip(base::Error),
|
||||
#[error("the given kernel command line was invalid: {0}")]
|
||||
Cmdline(kernel_cmdline::Error),
|
||||
#[error("unable to make an Event: {0}")]
|
||||
CreateEvent(base::Error),
|
||||
#[error("FDT could not be created: {0}")]
|
||||
CreateFdt(cros_fdt::Error),
|
||||
#[error("failed to create a PCI root hub: {0}")]
|
||||
CreatePciRoot(arch::DeviceRegistrationError),
|
||||
#[error("failed to create platform bus: {0}")]
|
||||
CreatePlatformBus(arch::DeviceRegistrationError),
|
||||
#[error("unable to create serial devices: {0}")]
|
||||
CreateSerialDevices(arch::DeviceRegistrationError),
|
||||
#[error("failed to create socket: {0}")]
|
||||
CreateSocket(io::Error),
|
||||
#[error("failed to create VCPU: {0}")]
|
||||
CreateVcpu(base::Error),
|
||||
#[error("vm created wrong kind of vcpu")]
|
||||
DowncastVcpu,
|
||||
#[error("failed to finalize devices: {0}")]
|
||||
FinalizeDevices(base::Error),
|
||||
#[error("failed to finalize IRQ chip: {0}")]
|
||||
FinalizeIrqChip(base::Error),
|
||||
#[error("failed to get serial cmdline: {0}")]
|
||||
GetSerialCmdline(GetSerialCmdlineError),
|
||||
#[error("Failed to get the timer base frequency: {0}")]
|
||||
GetTimebase(base::Error),
|
||||
#[error("Image type not supported on riscv")]
|
||||
ImageTypeUnsupported,
|
||||
#[error("initrd could not be loaded: {0}")]
|
||||
InitrdLoadFailure(arch::LoadImageError),
|
||||
#[error("kernel could not be loaded: {0}")]
|
||||
KernelLoadFailure(arch::LoadImageError),
|
||||
#[error("protected vms not supported on riscv(yet)")]
|
||||
ProtectedVmUnsupported,
|
||||
#[error("ramoops address is different from high_mmio_base: {0} vs {1}")]
|
||||
RamoopsAddress(u64, u64),
|
||||
#[error("failed to register irq fd: {0}")]
|
||||
RegisterIrqfd(base::Error),
|
||||
#[error("error registering PCI bus: {0}")]
|
||||
RegisterPci(BusError),
|
||||
#[error("error registering virtual socket device: {0}")]
|
||||
RegisterVsock(arch::DeviceRegistrationError),
|
||||
#[error("failed to set device attr: {0}")]
|
||||
SetDeviceAttr(base::Error),
|
||||
#[error("failed to set register: {0}")]
|
||||
SetReg(base::Error),
|
||||
#[error("Timebase frequency too large")]
|
||||
TimebaseTooLarge,
|
||||
#[error("this function isn't supported")]
|
||||
Unsupported,
|
||||
#[error("failed to initialize VCPU: {0}")]
|
||||
VcpuInit(base::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub struct Riscv64;
|
||||
|
||||
impl arch::LinuxArch for Riscv64 {
|
||||
type Error = Error;
|
||||
|
||||
/// Returns a Vec of the valid memory addresses.
|
||||
/// These should be used to configure the GuestMemory structure for the platfrom.
|
||||
fn guest_memory_layout(
|
||||
components: &VmComponents,
|
||||
_hypervisor: &impl Hypervisor,
|
||||
) -> std::result::Result<Vec<(GuestAddress, u64, MemoryRegionOptions)>, Self::Error> {
|
||||
Ok(vec![(
|
||||
GuestAddress(RISCV64_PHYS_MEM_START),
|
||||
components.memory_size,
|
||||
Default::default(),
|
||||
)])
|
||||
}
|
||||
|
||||
fn get_system_allocator_config<V: Vm>(vm: &V) -> SystemAllocatorConfig {
|
||||
get_resource_allocator_config(vm.get_memory().memory_size(), vm.get_guest_phys_addr_bits())
|
||||
}
|
||||
|
||||
fn build_vm<V, Vcpu>(
|
||||
mut components: VmComponents,
|
||||
_vm_evt_wrtube: &SendTube,
|
||||
system_allocator: &mut SystemAllocator,
|
||||
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
|
||||
serial_jail: Option<Minijail>,
|
||||
(_bat_type, _bat_jail): (Option<BatteryType>, Option<Minijail>),
|
||||
mut vm: V,
|
||||
ramoops_region: Option<arch::pstore::RamoopsRegion>,
|
||||
devices: Vec<(Box<dyn BusDeviceObj>, Option<Minijail>)>,
|
||||
irq_chip: &mut dyn IrqChipRiscv64,
|
||||
vcpu_ids: &mut Vec<usize>,
|
||||
_dump_device_tree_blob: Option<PathBuf>,
|
||||
_debugcon_jail: Option<Minijail>,
|
||||
#[cfg(feature = "swap")] swap_controller: Option<&swap::SwapController>,
|
||||
) -> std::result::Result<RunnableLinuxVm<V, Vcpu>, Self::Error>
|
||||
where
|
||||
V: VmRiscv64,
|
||||
Vcpu: VcpuRiscv64,
|
||||
{
|
||||
if components.hv_cfg.protection_type == ProtectionType::Protected {
|
||||
return Err(Error::ProtectedVmUnsupported);
|
||||
}
|
||||
|
||||
let mem = vm.get_memory().clone();
|
||||
|
||||
let mmio_bus = Arc::new(Bus::new());
|
||||
|
||||
// Riscv doesn't really use the io bus like x86, so just create an empty bus.
|
||||
let io_bus = Arc::new(Bus::new());
|
||||
|
||||
let com_evt_1_3 = Event::new().map_err(Error::CreateEvent)?;
|
||||
let com_evt_2_4 = Event::new().map_err(Error::CreateEvent)?;
|
||||
arch::add_serial_devices(
|
||||
components.hv_cfg.protection_type,
|
||||
&mmio_bus,
|
||||
&com_evt_1_3,
|
||||
&com_evt_2_4,
|
||||
serial_parameters,
|
||||
serial_jail,
|
||||
#[cfg(feature = "swap")]
|
||||
swap_controller,
|
||||
)
|
||||
.map_err(Error::CreateSerialDevices)?;
|
||||
|
||||
let (pci_devices, others): (Vec<_>, Vec<_>) = devices
|
||||
.into_iter()
|
||||
.partition(|(dev, _)| dev.as_pci_device().is_some());
|
||||
let pci_devices = pci_devices
|
||||
.into_iter()
|
||||
.map(|(dev, jail_orig)| (dev.into_pci_device().unwrap(), jail_orig))
|
||||
.collect();
|
||||
let (pci, pci_irqs, mut pid_debug_label_map, _amls) = arch::generate_pci_root(
|
||||
pci_devices,
|
||||
irq_chip.as_irq_chip_mut(),
|
||||
Arc::clone(&mmio_bus),
|
||||
Arc::clone(&io_bus),
|
||||
system_allocator,
|
||||
&mut vm,
|
||||
devices::IMSIC_MAX_INT_IDS as usize,
|
||||
None,
|
||||
#[cfg(feature = "swap")]
|
||||
swap_controller,
|
||||
)
|
||||
.map_err(Error::CreatePciRoot)?;
|
||||
|
||||
let pci_root = Arc::new(Mutex::new(pci));
|
||||
let pci_bus = Arc::new(Mutex::new(PciConfigMmio::new(pci_root.clone(), 8)));
|
||||
let (platform_devices, _others): (Vec<_>, Vec<_>) = others
|
||||
.into_iter()
|
||||
.partition(|(dev, _)| dev.as_platform_device().is_some());
|
||||
|
||||
let platform_devices = platform_devices
|
||||
.into_iter()
|
||||
.map(|(dev, jail_orig)| (*(dev.into_platform_device().unwrap()), jail_orig))
|
||||
.collect();
|
||||
let (platform_devices, mut platform_pid_debug_label_map) =
|
||||
arch::sys::unix::generate_platform_bus(
|
||||
platform_devices,
|
||||
irq_chip.as_irq_chip_mut(),
|
||||
&mmio_bus,
|
||||
system_allocator,
|
||||
#[cfg(feature = "swap")]
|
||||
swap_controller,
|
||||
)
|
||||
.map_err(Error::CreatePlatformBus)?;
|
||||
pid_debug_label_map.append(&mut platform_pid_debug_label_map);
|
||||
|
||||
let mut cmdline = get_base_linux_cmdline();
|
||||
|
||||
if let Some(ramoops_region) = ramoops_region {
|
||||
arch::pstore::add_ramoops_kernel_cmdline(&mut cmdline, &ramoops_region)
|
||||
.map_err(Error::Cmdline)?;
|
||||
}
|
||||
|
||||
mmio_bus
|
||||
.insert(pci_bus, RISCV64_PCI_CFG_BASE, RISCV64_PCI_CFG_SIZE)
|
||||
.map_err(Error::RegisterPci)?;
|
||||
|
||||
get_serial_cmdline(&mut cmdline, serial_parameters, "mmio")
|
||||
.map_err(Error::GetSerialCmdline)?;
|
||||
for param in components.extra_kernel_params {
|
||||
cmdline.insert_str(¶m).map_err(Error::Cmdline)?;
|
||||
}
|
||||
|
||||
// Event used by PMDevice to notify crosvm that guest OS is trying to suspend.
|
||||
let suspend_evt = Event::new().map_err(Error::CreateEvent)?;
|
||||
|
||||
// separate out image loading from other setup to get a specific error for
|
||||
// image loading
|
||||
let initrd;
|
||||
let kernel_initrd_end = match components.vm_image {
|
||||
VmImage::Bios(ref _bios) => {
|
||||
return Err(Error::ImageTypeUnsupported);
|
||||
}
|
||||
VmImage::Kernel(ref mut kernel_image) => {
|
||||
let kernel_size =
|
||||
arch::load_image(&mem, kernel_image, get_kernel_addr(), u64::max_value())
|
||||
.map_err(Error::KernelLoadFailure)?;
|
||||
let kernel_end = get_kernel_addr().offset() + kernel_size as u64;
|
||||
initrd = match components.initrd_image {
|
||||
Some(initrd_file) => {
|
||||
let mut initrd_file = initrd_file;
|
||||
let initrd_addr =
|
||||
(kernel_end + (RISCV64_INITRD_ALIGN - 1)) & !(RISCV64_INITRD_ALIGN - 1);
|
||||
let initrd_max_size =
|
||||
components.memory_size - (initrd_addr - RISCV64_PHYS_MEM_START);
|
||||
let initrd_addr = GuestAddress(initrd_addr);
|
||||
let initrd_size =
|
||||
arch::load_image(&mem, &mut initrd_file, initrd_addr, initrd_max_size)
|
||||
.map_err(Error::InitrdLoadFailure)?;
|
||||
Some((initrd_addr, initrd_size))
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
if let Some((initrd_addr, initrd_size)) = initrd {
|
||||
initrd_addr.offset() + initrd_size as u64 - RISCV64_PHYS_MEM_START
|
||||
} else {
|
||||
kernel_end - RISCV64_PHYS_MEM_START
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Creates vcpus early as the irqchip needs them created to attach interrupts.
|
||||
let vcpu_count = components.vcpu_count;
|
||||
let mut vcpus = Vec::with_capacity(vcpu_count);
|
||||
for vcpu_id in 0..vcpu_count {
|
||||
let vcpu: Vcpu = *vm
|
||||
.create_vcpu(vcpu_id)
|
||||
.map_err(Error::CreateVcpu)?
|
||||
.downcast::<Vcpu>()
|
||||
.map_err(|_| Error::DowncastVcpu)?;
|
||||
vcpus.push(vcpu);
|
||||
vcpu_ids.push(vcpu_id);
|
||||
}
|
||||
|
||||
irq_chip.finalize().map_err(Error::FinalizeIrqChip)?;
|
||||
|
||||
irq_chip
|
||||
.finalize_devices(system_allocator, &io_bus, &mmio_bus)
|
||||
.map_err(Error::FinalizeDevices)?;
|
||||
let (aia_num_ids, aia_num_sources) = irq_chip.get_num_ids_sources();
|
||||
|
||||
let pci_cfg = fdt::PciConfigRegion {
|
||||
base: RISCV64_PCI_CFG_BASE,
|
||||
size: RISCV64_PCI_CFG_SIZE,
|
||||
};
|
||||
|
||||
let pci_ranges: Vec<fdt::PciRange> = system_allocator
|
||||
.mmio_pools()
|
||||
.iter()
|
||||
.map(|range| fdt::PciRange {
|
||||
space: fdt::PciAddressSpace::Memory64,
|
||||
bus_address: range.start,
|
||||
cpu_physical_address: range.start,
|
||||
size: range.len().unwrap(),
|
||||
prefetchable: false,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let fdt_offset = (kernel_initrd_end + (RISCV64_FDT_ALIGN - 1)) & !(RISCV64_FDT_ALIGN - 1);
|
||||
|
||||
let timebase_freq: u32 = vcpus[0]
|
||||
.get_one_reg(VcpuRegister::Timer(TimerRegister::TimebaseFrequency))
|
||||
.map_err(Error::GetTimebase)?
|
||||
.try_into()
|
||||
.map_err(|_| Error::TimebaseTooLarge)?;
|
||||
|
||||
fdt::create_fdt(
|
||||
RISCV64_FDT_MAX_SIZE as usize,
|
||||
&mem,
|
||||
pci_irqs,
|
||||
pci_cfg,
|
||||
&pci_ranges,
|
||||
components.vcpu_count as u32,
|
||||
fdt_offset,
|
||||
aia_num_ids,
|
||||
aia_num_sources,
|
||||
cmdline.as_str(),
|
||||
initrd,
|
||||
timebase_freq,
|
||||
)
|
||||
.map_err(Error::CreateFdt)?;
|
||||
|
||||
let vcpu_init = vec![
|
||||
VcpuInitRiscv64::new(GuestAddress(fdt_offset + RISCV64_PHYS_MEM_START));
|
||||
vcpu_count
|
||||
];
|
||||
|
||||
Ok(RunnableLinuxVm {
|
||||
vm,
|
||||
vcpu_count: components.vcpu_count,
|
||||
vcpus: Some(vcpus),
|
||||
vcpu_init,
|
||||
vcpu_affinity: components.vcpu_affinity,
|
||||
no_smt: false,
|
||||
irq_chip: irq_chip.try_box_clone().map_err(Error::CloneIrqChip)?,
|
||||
has_bios: false,
|
||||
io_bus,
|
||||
mmio_bus,
|
||||
pid_debug_label_map,
|
||||
resume_notify_devices: Vec::new(),
|
||||
root_config: pci_root,
|
||||
platform_devices,
|
||||
hotplug_bus: BTreeMap::new(),
|
||||
rt_cpus: components.rt_cpus,
|
||||
delay_rt: components.delay_rt,
|
||||
suspend_evt,
|
||||
bat_control: None,
|
||||
pm: None,
|
||||
devices_thread: None,
|
||||
vm_request_tube: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn configure_vcpu<V: Vm>(
|
||||
_vm: &V,
|
||||
_hypervisor: &dyn Hypervisor,
|
||||
_irq_chip: &mut dyn IrqChipRiscv64,
|
||||
vcpu: &mut dyn VcpuRiscv64,
|
||||
_vcpu_init: VcpuInitRiscv64,
|
||||
vcpu_id: usize,
|
||||
_num_cpus: usize,
|
||||
_has_bios: bool,
|
||||
cpu_config: Option<CpuConfigRiscv64>,
|
||||
) -> std::result::Result<(), Self::Error> {
|
||||
vcpu.set_one_reg(VcpuRegister::Core(CoreRegister::Pc), get_kernel_addr().0)
|
||||
.map_err(Self::Error::SetReg)?;
|
||||
vcpu.set_one_reg(VcpuRegister::Core(CoreRegister::A0), vcpu_id as u64)
|
||||
.map_err(Self::Error::SetReg)?;
|
||||
vcpu.set_one_reg(
|
||||
VcpuRegister::Core(CoreRegister::A1),
|
||||
cpu_config.unwrap().fdt_address.0,
|
||||
)
|
||||
.map_err(Self::Error::SetReg)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_pci_device<V: VmRiscv64, Vcpu: VcpuRiscv64>(
|
||||
_linux: &mut RunnableLinuxVm<V, Vcpu>,
|
||||
_device: Box<dyn PciDevice>,
|
||||
_minijail: Option<Minijail>,
|
||||
_resources: &mut SystemAllocator,
|
||||
_tube: &mpsc::Sender<PciRootCommand>,
|
||||
#[cfg(feature = "swap")] _swap_controller: Option<&swap::SwapController>,
|
||||
) -> std::result::Result<PciAddress, Self::Error> {
|
||||
// hotplug function isn't verified on Riscv64, so set it unsupported here.
|
||||
Err(Error::Unsupported)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_high_mmio_base_size(mem_size: u64, guest_phys_addr_bits: u8) -> (u64, u64) {
|
||||
let guest_phys_end = 1u64 << guest_phys_addr_bits;
|
||||
let high_mmio_base = RISCV64_PHYS_MEM_START + mem_size;
|
||||
let size = guest_phys_end
|
||||
.checked_sub(high_mmio_base)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"guest_phys_end {:#x} < high_mmio_base {:#x}",
|
||||
guest_phys_end, high_mmio_base,
|
||||
);
|
||||
});
|
||||
(high_mmio_base, size)
|
||||
}
|
||||
|
||||
fn get_base_linux_cmdline() -> kernel_cmdline::Cmdline {
|
||||
let mut cmdline = kernel_cmdline::Cmdline::new(base::pagesize());
|
||||
cmdline.insert_str("panic=-1").unwrap();
|
||||
cmdline
|
||||
}
|
||||
|
||||
/// Returns a system resource allocator coniguration.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `mem_size` - Size of guest memory (RAM) in bytes.
|
||||
/// * `guest_phys_addr_bits` - Size of guest physical addresses (IPA) in bits.
|
||||
fn get_resource_allocator_config(mem_size: u64, guest_phys_addr_bits: u8) -> SystemAllocatorConfig {
|
||||
let (high_mmio_base, high_mmio_size) = get_high_mmio_base_size(mem_size, guest_phys_addr_bits);
|
||||
SystemAllocatorConfig {
|
||||
io: None,
|
||||
low_mmio: AddressRange::from_start_and_size(RISCV64_MMIO_BASE, RISCV64_MMIO_SIZE)
|
||||
.expect("invalid mmio region"),
|
||||
high_mmio: AddressRange::from_start_and_size(high_mmio_base, high_mmio_size)
|
||||
.expect("invalid high mmio region"),
|
||||
platform_mmio: None,
|
||||
first_irq: RISCV64_IRQ_BASE,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MsrHandlers;
|
||||
|
||||
impl MsrHandlers {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub fn read(&self, _index: u32) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn write(&self, _index: u32, _data: u64) -> Option<()> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn add_handler(
|
||||
&mut self,
|
||||
_index: u32,
|
||||
_msr_config: MsrConfig,
|
||||
_cpu_id: usize,
|
||||
) -> std::result::Result<(), MsrExitHandlerError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue