Files
RedBear-OS/src/numa.rs
T
R Aadarsh 9ecc75029c * Correctly put cpus and memories for adoption
* Test on x86_64 (works)
2026-06-21 14:03:49 +05:30

194 lines
5.5 KiB
Rust

#[cfg(all(
feature = "acpi",
any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")
))]
use crate::acpi;
use crate::{
cpu_set::LogicalCpuId,
sync::{CleanLockToken, Mutex, L0},
};
use alloc::{sync::Arc, vec::Vec};
use hashbrown::HashMap;
use spin::once::Once;
pub static NUMA_NODES: Once<HashMap<u32, NumaNode>> = Once::new();
pub static NUMBER_OF_DOMAINS: Once<u32> = Once::new();
#[derive(Debug)]
pub struct NumaMemory {
pub start: usize,
pub length: usize,
}
#[derive(Debug)]
pub struct NumaCpu {
pub id: u32,
}
#[derive(Default, Debug)]
/// Represents a single NUMA logical node. A node is different from a domain. NUMA domain
/// refers to what exists physically. A NUMA node on the other hand is a logical one, with domains having
/// only CPUs or memory grouped together with other CPUs or memories.
///
/// See the function `reorganise` below.
pub struct NumaNode {
cpus: Vec<NumaCpu>,
memory: Vec<NumaMemory>,
distances: Vec<(u32, u8)>,
}
pub fn init() {
NUMA_NODES.call_once(|| HashMap::new());
let mut flag = false;
#[cfg(all(
feature = "acpi",
any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")
))]
{
acpi::srat::init();
acpi::slit::init();
flag = true;
}
#[cfg(any(target_arch = "riscv64", target_arch = "aarch64"))]
{
if !flag {
todo!()
}
}
unsafe {
sort_by_distances();
reorganise();
shrink();
}
// From this point onwards, the global static `NUMA_NODES` or any of its elements
// MUST NOT be mutated by the usual unsafe magic that functions in this file use.
}
pub unsafe fn add_cpu(id: u32, node_id: u32) {
let numa_nodes = NUMA_NODES.get().unwrap();
let numa_nodes = unsafe { &mut *(&raw const *numa_nodes as *mut HashMap<u32, NumaNode>) };
if let Some(node) = numa_nodes.get_mut(&id) {
node.cpus.push(NumaCpu { id });
} else {
let mut cpus = Vec::new();
cpus.push(NumaCpu { id });
numa_nodes.insert(
node_id,
NumaNode {
cpus,
memory: Vec::new(),
distances: Vec::new(),
},
);
}
}
pub unsafe fn add_memory(node_id: u32, start: usize, length: usize) {
let numa_nodes = NUMA_NODES.get().unwrap();
let numa_nodes = unsafe { &mut *(&raw const *numa_nodes as *mut HashMap<u32, NumaNode>) };
if let Some(node) = numa_nodes.get_mut(&node_id) {
node.memory.push(NumaMemory { start, length });
} else {
let mut memory = Vec::new();
memory.push(NumaMemory { start, length });
numa_nodes.insert(
node_id,
NumaNode {
cpus: Vec::new(),
memory,
distances: Vec::new(),
},
);
}
}
pub unsafe fn set_distance(src: u32, target: u32, distance: u8) {
let nodes =
unsafe { &mut *(&raw const *(NUMA_NODES.get().unwrap()) as *mut HashMap<u32, NumaNode>) };
let src = nodes.get_mut(&src).unwrap();
src.distances.push((target, distance));
}
unsafe fn shrink() {
let nodes =
unsafe { &mut *(&raw const *(NUMA_NODES.get().unwrap()) as *mut HashMap<u32, NumaNode>) };
nodes.shrink_to_fit();
for (id, node) in nodes {
node.cpus.shrink_to_fit();
node.distances.shrink_to_fit();
node.memory.shrink_to_fit();
}
}
/// Reorganises CPUs and memories into nodes. If a NUMA domain has only a CPU but no memory, it is
/// put into a node with a memory that is nearest to it. Similarly, if a NUMA domain has only memory but no
/// CPUs, the memory is put into a node that has a CPU that is nearest to it.
///
/// See the comment above the definition of `NumaNode`.
unsafe fn reorganise() {
let nodes =
unsafe { &mut *(&raw const *(NUMA_NODES.get().unwrap()) as *mut HashMap<u32, NumaNode>) };
let ids = nodes.keys().map(|e| *e).collect::<Vec<u32>>();
for id in ids {
let node = nodes.remove(&id).unwrap();
if node.cpus.len() == 0 {
assert!(node.memory.len() != 0);
put_for_adoption(nodes, node.distances, Some(node.memory), None, id);
} else if node.memory.len() == 0 {
put_for_adoption(nodes, node.distances, None, Some(node.cpus), id);
} else {
nodes.insert(id, node);
}
}
}
fn put_for_adoption(
nodes: &mut HashMap<u32, NumaNode>,
distances: Vec<(u32, u8)>,
memories: Option<Vec<NumaMemory>>,
cpus: Option<Vec<NumaCpu>>,
orphan_node_id: u32,
) {
if let Some(memories) = memories {
assert!(cpus.is_none());
let (nearest_node_id, distance) = distances.first().unwrap();
let nearest_node = nodes.get_mut(nearest_node_id).unwrap();
nearest_node.memory.extend(memories);
} else if let Some(cpus) = cpus {
assert!(memories.is_none());
let (nearest_node_id, distance) = distances.first().unwrap();
let nearest_node = nodes.get_mut(nearest_node_id).unwrap();
nearest_node.cpus.extend(cpus);
} else {
panic!()
};
for (_, node) in nodes {
if let Some(idx) = node.distances.iter().position(|e| e.0 == orphan_node_id) {
let _ = node.distances.remove(idx);
}
}
}
unsafe fn sort_by_distances() {
let nodes =
unsafe { &mut *(&raw const *(NUMA_NODES.get().unwrap()) as *mut HashMap<u32, NumaNode>) };
for (id, node) in nodes {
node.distances.sort_by_key(|(_, e)| *e);
}
}