From 063aa4b7d53acedfaae8d8b54f84cab60595a4c7 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Tue, 11 Nov 2025 15:48:42 +0000 Subject: [PATCH] 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 --- arch/src/riscv64/fdt.rs | 7 ++--- arch/src/riscv64/mod.rs | 57 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/arch/src/riscv64/fdt.rs b/arch/src/riscv64/fdt.rs index ee453eb2f..e78138861 100644 --- a/arch/src/riscv64/fdt.rs +++ b/arch/src/riscv64/fdt.rs @@ -65,6 +65,7 @@ pub fn create_fdt, aia_device: &Arc>, initrd: &Option, @@ -84,7 +85,7 @@ pub fn create_fdt, 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)?; diff --git a/arch/src/riscv64/mod.rs b/arch/src/riscv64/mod.rs index b4478aaa6..b4feb99d5 100644 --- a/arch/src/riscv64/mod.rs +++ b/arch/src/riscv64/mod.rs @@ -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 { + 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 = + 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( @@ -115,10 +170,12 @@ pub fn configure_system>, ) -> 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,