arch: riscv64: Expose host extension set to guest via FDT

The set of extensions supported by a RISC-V system needs to be exposed
to the guest - currently that is a fixed, minimal set of extensions.
These extensions are not sufficient to boot Ubuntu 25.10 which now has a
mininimum requirement of RVA23S64 (which is a minimum set of extensions
that make sense for server use cases.)

The easiest way to convey the extensions that the guest should use is to
copy those that the host kernel understands (and thus includes in the
/proc/cpuinfo) data.

However since nested virtualisation is not currently possible - exclude
the "H" (Hypervisor) extension from the list of short (single letter)
extensions.

Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
This commit is contained in:
Rob Bradford
2025-11-11 15:48:42 +00:00
parent d586c844de
commit 063aa4b7d5
2 changed files with 61 additions and 3 deletions

View File

@@ -65,6 +65,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
guest_mem: &GuestMemoryMmap,
cmdline: &str,
num_vcpu: u32,
isa_string: &str,
device_info: &HashMap<(DeviceType, String), T, S>,
aia_device: &Arc<Mutex<dyn Vaia>>,
initrd: &Option<InitramfsConfig>,
@@ -84,7 +85,7 @@ pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHash
// Properties
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
fdt.property_u32("#size-cells", SIZE_CELLS)?;
create_cpu_nodes(&mut fdt, num_vcpu)?;
create_cpu_nodes(&mut fdt, num_vcpu, isa_string)?;
create_memory_node(&mut fdt, guest_mem)?;
create_chosen_node(&mut fdt, cmdline, initrd)?;
create_aia_node(&mut fdt, aia_device)?;
@@ -108,7 +109,7 @@ pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> R
}
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> FdtWriterResult<()> {
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32, isa_string: &str) -> FdtWriterResult<()> {
// See https://elixir.bootlin.com/linux/v6.10/source/Documentation/devicetree/bindings/riscv/cpus.yaml
let cpus = fdt.begin_node("cpus")?;
// As per documentation, on RISC-V 64-bit systems value should be set to 1.
@@ -123,7 +124,7 @@ fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> FdtWriterResult<()> {
fdt.property_string("device_type", "cpu")?;
fdt.property_string("compatible", "riscv")?;
fdt.property_string("mmu-type", "sv48")?;
fdt.property_string("riscv,isa", "rv64imafdc_smaia_ssaia")?;
fdt.property_string("riscv,isa", isa_string)?;
fdt.property_string("status", "okay")?;
fdt.property_u32("reg", cpu_index)?;
fdt.property_u32("phandle", CPU_BASE_PHANDLE + cpu_index)?;

View File

@@ -12,6 +12,8 @@ pub mod uefi;
use std::collections::HashMap;
use std::fmt::Debug;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::sync::{Arc, Mutex};
use hypervisor::arch::riscv64::aia::Vaia;
@@ -51,6 +53,22 @@ pub enum Error {
/// Error configuring the general purpose registers
#[error("Error configuring the general purpose registers")]
RegsConfiguration(#[source] hypervisor::HypervisorCpuError),
/// Error opening /proc/cpuinfo
#[error("Error opening /proc/cpuinfo")]
OpenCpuInfo(#[source] std::io::Error),
/// Error reading /proc/cpuinfo
#[error("Error reading /proc/cpuinfo")]
ReadCpuInfo(#[source] std::io::Error),
/// Invalid ISA string
#[error("Invalid ISA string: {0}")]
InvalidIsaString(String),
/// Error parsing /proc/cpuinfo
#[error("Error parsing /proc/cpuinfo")]
CpuInfoParsing,
}
#[derive(Debug, Copy, Clone)]
@@ -104,6 +122,43 @@ pub fn arch_memory_regions() -> Vec<(GuestAddress, usize, RegionType)> {
]
}
// Read the first "isa" string from /proc/cpuinfo and filter out the H extension,
// while correctly preserving multi-letter extensions.
fn isa_string_from_host() -> Result<String, Error> {
let file = File::open("/proc/cpuinfo").map_err(Error::OpenCpuInfo)?;
let reader = BufReader::new(file);
for line in reader.lines() {
let line = line.map_err(Error::ReadCpuInfo)?;
let trimmed_line = line.trim();
if trimmed_line.starts_with("isa") {
let parts: Vec<&str> = trimmed_line.split(':').collect();
if parts.len() == 2 {
let isa_string = parts[1].trim();
// Split the string by underscores to separate single letter vs long-form
// extensions
let mut components: Vec<String> =
isa_string.split('_').map(|s| s.to_string()).collect();
if components.is_empty() {
return Err(Error::InvalidIsaString(isa_string.to_string()));
}
// Remove H extension if present in single letter extensions
let first_component = components[0].chars().filter(|&c| c != 'h').collect();
components[0] = first_component;
return Ok(components.join("_"));
}
}
}
Err(Error::CpuInfoParsing)
}
/// Configures the system and should be called once per vm before starting vcpu threads.
#[allow(clippy::too_many_arguments)]
pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
@@ -115,10 +170,12 @@ pub fn configure_system<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::Bui
pci_space_info: &[PciSpaceInfo],
aia_device: &Arc<Mutex<dyn Vaia>>,
) -> super::Result<()> {
let isa_string = isa_string_from_host()?;
let fdt_final = fdt::create_fdt(
guest_mem,
cmdline,
num_vcpu,
&isa_string,
device_info,
aia_device,
initrd,