pvmfw: Parse cpu-map DT node for the guest
Support a subset of all possible topologies[*] where the guest sees a
set of clusters of cores with the usual read-then-patch approach. Note
that no separate validation is required as parsing ensures that the
nodes within a cluster properly start from 0 and are contiguous (same
for clusters) and that they point to valid CPU nodes. Extra properties,
if any are ignored.
Add labels to the cpu nodes so that DTC generates phandles for them,
which pvmfw can then use when patching the cpu-map node in the template
DT.
[*]: https://www.kernel.org/doc/Documentation/devicetree/bindings/cpu/cpu-topology.txt
Bug: 284369518
Test: m pvmfw
Change-Id: Ia70a73545b1740a3343ce7f0e4549da456cd6d55
diff --git a/pvmfw/platform.dts b/pvmfw/platform.dts
index 569d20a..275a1c9 100644
--- a/pvmfw/platform.dts
+++ b/pvmfw/platform.dts
@@ -52,7 +52,35 @@
cpus {
#address-cells = <1>;
#size-cells = <0>;
- cpu@0 {
+
+ cpu-map {
+ cluster0 {
+ core0 { cpu = <PLACEHOLDER>; };
+ core1 { cpu = <PLACEHOLDER>; };
+ core2 { cpu = <PLACEHOLDER>; };
+ core3 { cpu = <PLACEHOLDER>; };
+ core4 { cpu = <PLACEHOLDER>; };
+ core5 { cpu = <PLACEHOLDER>; };
+ };
+ cluster1 {
+ core0 { cpu = <PLACEHOLDER>; };
+ core1 { cpu = <PLACEHOLDER>; };
+ core2 { cpu = <PLACEHOLDER>; };
+ core3 { cpu = <PLACEHOLDER>; };
+ core4 { cpu = <PLACEHOLDER>; };
+ core5 { cpu = <PLACEHOLDER>; };
+ };
+ cluster2 {
+ core0 { cpu = <PLACEHOLDER>; };
+ core1 { cpu = <PLACEHOLDER>; };
+ core2 { cpu = <PLACEHOLDER>; };
+ core3 { cpu = <PLACEHOLDER>; };
+ core4 { cpu = <PLACEHOLDER>; };
+ core5 { cpu = <PLACEHOLDER>; };
+ };
+ };
+
+ cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -84,7 +112,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@1 {
+ cpu1: cpu@1 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -116,7 +144,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@2 {
+ cpu2: cpu@2 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -148,7 +176,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@3 {
+ cpu3: cpu@3 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -180,7 +208,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@4 {
+ cpu4: cpu@4 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -212,7 +240,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@5 {
+ cpu5: cpu@5 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -244,7 +272,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@6 {
+ cpu6: cpu@6 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -276,7 +304,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@7 {
+ cpu7: cpu@7 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -308,7 +336,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@8 {
+ cpu8: cpu@8 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -340,7 +368,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@9 {
+ cpu9: cpu@9 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -372,7 +400,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@10 {
+ cpu10: cpu@10 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -404,7 +432,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@11 {
+ cpu11: cpu@11 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -436,7 +464,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@12 {
+ cpu12: cpu@12 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -468,7 +496,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@13 {
+ cpu13: cpu@13 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -500,7 +528,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@14 {
+ cpu14: cpu@14 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
@@ -532,7 +560,7 @@
opp20 { opp-hz = <PLACEHOLDER2>; };
};
};
- cpu@15 {
+ cpu15: cpu@15 {
device_type = "cpu";
compatible = "arm,arm-v8";
enable-method = "psci";
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index ba0a992..47f4cdd 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -21,6 +21,7 @@
use crate::RebootReason;
use alloc::collections::BTreeMap;
use alloc::ffi::CString;
+use alloc::format;
use alloc::vec::Vec;
use core::cmp::max;
use core::cmp::min;
@@ -37,6 +38,7 @@
use libfdt::FdtError;
use libfdt::FdtNode;
use libfdt::FdtNodeMut;
+use libfdt::Phandle;
use log::debug;
use log::error;
use log::info;
@@ -200,10 +202,63 @@
Ok(table)
}
-fn read_cpu_info_from(fdt: &Fdt) -> libfdt::Result<ArrayVec<[CpuInfo; DeviceTreeInfo::MAX_CPUS]>> {
+#[derive(Debug, Default)]
+struct ClusterTopology {
+ // TODO: Support multi-level clusters & threads.
+ cores: [Option<usize>; ClusterTopology::MAX_CORES_PER_CLUSTER],
+}
+
+impl ClusterTopology {
+ const MAX_CORES_PER_CLUSTER: usize = 6;
+}
+
+#[derive(Debug, Default)]
+struct CpuTopology {
+ // TODO: Support sockets.
+ clusters: [Option<ClusterTopology>; CpuTopology::MAX_CLUSTERS],
+}
+
+impl CpuTopology {
+ const MAX_CLUSTERS: usize = 3;
+}
+
+fn read_cpu_map_from(fdt: &Fdt) -> libfdt::Result<Option<BTreeMap<Phandle, (usize, usize)>>> {
+ let Some(cpu_map) = fdt.node(cstr!("/cpus/cpu-map"))? else {
+ return Ok(None);
+ };
+
+ let mut topology = BTreeMap::new();
+ for n in 0..CpuTopology::MAX_CLUSTERS {
+ let name = CString::new(format!("cluster{n}")).unwrap();
+ let Some(cluster) = cpu_map.subnode(&name)? else {
+ break;
+ };
+ for m in 0..ClusterTopology::MAX_CORES_PER_CLUSTER {
+ let name = CString::new(format!("core{n}")).unwrap();
+ let Some(core) = cluster.subnode(&name)? else {
+ break;
+ };
+ let cpu = core.getprop_u32(cstr!("cpu"))?.ok_or(FdtError::NotFound)?;
+ let prev = topology.insert(cpu.try_into()?, (n, m));
+ if prev.is_some() {
+ return Err(FdtError::BadValue);
+ }
+ }
+ }
+
+ Ok(Some(topology))
+}
+
+fn read_cpu_info_from(
+ fdt: &Fdt,
+) -> libfdt::Result<(ArrayVec<[CpuInfo; DeviceTreeInfo::MAX_CPUS]>, Option<CpuTopology>)> {
let mut cpus = ArrayVec::new();
+
+ let cpu_map = read_cpu_map_from(fdt)?;
+ let mut topology: CpuTopology = Default::default();
+
let mut cpu_nodes = fdt.compatible_nodes(cstr!("arm,arm-v8"))?;
- for cpu in cpu_nodes.by_ref().take(cpus.capacity()) {
+ for (idx, cpu) in cpu_nodes.by_ref().take(cpus.capacity()).enumerate() {
let cpu_capacity = cpu.getprop_u32(cstr!("capacity-dmips-mhz"))?;
let opp_phandle = cpu.getprop_u32(cstr!("operating-points-v2"))?;
let opptable_info = if let Some(phandle) = opp_phandle {
@@ -215,12 +270,24 @@
};
let info = CpuInfo { opptable_info, cpu_capacity };
cpus.push(info);
+
+ if let Some(ref cpu_map) = cpu_map {
+ let phandle = cpu.get_phandle()?.ok_or(FdtError::NotFound)?;
+ let (cluster, core) = cpu_map.get(&phandle).ok_or(FdtError::BadValue)?;
+ let cluster = topology.clusters[*cluster].get_or_insert(Default::default());
+ let mut core = cluster.cores[*core];
+ if core.is_some() {
+ return Err(FdtError::BadValue);
+ }
+ let _ = core.insert(idx);
+ }
}
+
if cpu_nodes.next().is_some() {
warn!("DT has more than {} CPU nodes: discarding extra nodes.", cpus.capacity());
}
- Ok(cpus)
+ Ok((cpus, cpu_map.map(|_| topology)))
}
fn validate_cpu_info(cpus: &[CpuInfo]) -> Result<(), FdtValidationError> {
@@ -304,10 +371,17 @@
Ok(node)
}
-fn patch_cpus(fdt: &mut Fdt, cpus: &[CpuInfo]) -> libfdt::Result<()> {
+fn patch_cpus(
+ fdt: &mut Fdt,
+ cpus: &[CpuInfo],
+ topology: &Option<CpuTopology>,
+) -> libfdt::Result<()> {
const COMPAT: &CStr = cstr!("arm,arm-v8");
+ let mut cpu_phandles = Vec::new();
for (idx, cpu) in cpus.iter().enumerate() {
let mut cur = get_nth_compatible(fdt, idx, COMPAT)?.ok_or(FdtError::NoSpace)?;
+ let phandle = cur.as_node().get_phandle()?.unwrap();
+ cpu_phandles.push(phandle);
if let Some(cpu_capacity) = cpu.cpu_capacity {
cur.setprop_inplace(cstr!("capacity-dmips-mhz"), &cpu_capacity.to_be_bytes())?;
}
@@ -317,6 +391,33 @@
while let Some(current) = next {
next = current.delete_and_next_compatible(COMPAT)?;
}
+
+ if let Some(topology) = topology {
+ for (n, cluster) in topology.clusters.iter().enumerate() {
+ let path = CString::new(format!("/cpus/cpu-map/cluster{n}")).unwrap();
+ let cluster_node = fdt.node_mut(&path)?.unwrap();
+ if let Some(cluster) = cluster {
+ let mut iter = cluster_node.first_subnode()?;
+ for core in cluster.cores {
+ let mut core_node = iter.unwrap();
+ iter = if let Some(core_idx) = core {
+ let phandle = *cpu_phandles.get(core_idx).unwrap();
+ let value = u32::from(phandle).to_be_bytes();
+ core_node.setprop_inplace(cstr!("cpu"), &value)?;
+ core_node.next_subnode()?
+ } else {
+ core_node.delete_and_next_subnode()?
+ };
+ }
+ assert!(iter.is_none());
+ } else {
+ cluster_node.nop()?;
+ }
+ }
+ } else {
+ fdt.node_mut(cstr!("/cpus/cpu-map"))?.unwrap().nop()?;
+ }
+
Ok(())
}
@@ -759,6 +860,7 @@
pub memory_range: Range<usize>,
bootargs: Option<CString>,
cpus: ArrayVec<[CpuInfo; DeviceTreeInfo::MAX_CPUS]>,
+ cpu_topology: Option<CpuTopology>,
pci_info: PciInfo,
serial_info: SerialInfo,
pub swiotlb_info: SwiotlbInfo,
@@ -868,7 +970,7 @@
RebootReason::InvalidFdt
})?;
- let cpus = read_cpu_info_from(fdt).map_err(|e| {
+ let (cpus, cpu_topology) = read_cpu_info_from(fdt).map_err(|e| {
error!("Failed to read CPU info from DT: {e}");
RebootReason::InvalidFdt
})?;
@@ -933,6 +1035,7 @@
memory_range,
bootargs,
cpus,
+ cpu_topology,
pci_info,
serial_info,
swiotlb_info,
@@ -959,7 +1062,7 @@
RebootReason::InvalidFdt
})?;
}
- patch_cpus(fdt, &info.cpus).map_err(|e| {
+ patch_cpus(fdt, &info.cpus, &info.cpu_topology).map_err(|e| {
error!("Failed to patch cpus to DT: {e}");
RebootReason::InvalidFdt
})?;