use crate::GfxTarget;
#[allow(dead_code)] const TOPOLOGY: &str = "/sys/class/kfd/kfd/topology/nodes";
#[allow(dead_code)] #[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ComputeNode {
pub render_minor: u32,
pub name: Option<String>,
pub gfx_target: GfxTarget,
}
#[allow(dead_code)] fn property(content: &str, key: &str) -> Option<u64> {
for line in content.lines() {
let mut parts = line.split_whitespace();
if parts.next() == Some(key) {
return parts.next()?.parse().ok();
}
}
None
}
#[allow(dead_code)] fn parse_gfx_target(version: u64) -> Option<GfxTarget> {
if version == 0 {
return None;
}
let field = |v: u64| u32::try_from(v).ok();
Some(GfxTarget::new(
field(version / 10_000)?,
field(version / 100 % 100)?,
field(version % 100)?,
))
}
#[cfg(target_os = "linux")]
pub(crate) fn nodes() -> Vec<ComputeNode> {
let mut nodes = Vec::new();
let Ok(entries) = std::fs::read_dir(TOPOLOGY) else {
return nodes;
};
for entry in entries.flatten() {
let dir = entry.path();
let Ok(properties) = std::fs::read_to_string(dir.join("properties")) else {
continue;
};
let Some(gfx_target) =
property(&properties, "gfx_target_version").and_then(parse_gfx_target)
else {
continue;
};
let Some(render_minor) = property(&properties, "drm_render_minor")
.and_then(|m| u32::try_from(m).ok())
.filter(|&m| m > 0)
else {
continue;
};
nodes.push(ComputeNode {
render_minor,
name: std::fs::read_to_string(dir.join("name"))
.ok()
.map(|n| n.trim().to_string())
.filter(|n| !n.is_empty()),
gfx_target,
});
}
nodes
}
#[allow(dead_code)]
#[cfg(not(target_os = "linux"))]
pub(crate) fn nodes() -> Vec<ComputeNode> {
Vec::new()
}
#[cfg(test)]
mod tests {
use super::*;
const NODE: &str = "cpu_cores_count 0\nsimd_count 48\ngfx_target_version 100103\n\
vendor_id 4098\ndevice_id 5118\ndrm_render_minor 128\nnum_xcc 1\n";
#[test]
fn reads_properties_by_key() {
assert_eq!(property(NODE, "gfx_target_version"), Some(100_103));
assert_eq!(property(NODE, "drm_render_minor"), Some(128));
assert_eq!(property(NODE, "simd_count"), Some(48));
assert_eq!(property(NODE, "absent"), None);
}
#[test]
fn property_matches_whole_key_only() {
assert_eq!(property("simd_count_base 7\n", "simd_count"), None);
assert_eq!(property("simd 1\nsimd_count 48\n", "simd_count"), Some(48));
}
#[test]
fn property_tolerates_malformed_lines() {
assert_eq!(property("gfx_target_version\n", "gfx_target_version"), None);
assert_eq!(
property("gfx_target_version x\n", "gfx_target_version"),
None
);
assert_eq!(property("", "gfx_target_version"), None);
}
#[test]
fn decodes_gfx_target_versions() {
assert_eq!(parse_gfx_target(100_103), Some(GfxTarget::new(10, 1, 3)));
assert_eq!(parse_gfx_target(100_300), Some(GfxTarget::new(10, 3, 0)));
assert_eq!(parse_gfx_target(90_010), Some(GfxTarget::new(9, 0, 10)));
assert_eq!(parse_gfx_target(110_000), Some(GfxTarget::new(11, 0, 0)));
}
#[test]
fn cpu_nodes_have_no_gfx_target() {
assert_eq!(parse_gfx_target(0), None);
}
#[test]
fn node_listing_never_panics() {
for node in nodes() {
assert!(node.render_minor > 0);
assert!(node.gfx_target.major > 0);
assert!(node.name.as_ref().is_none_or(|n| !n.is_empty()));
}
}
}