Skip to main content

acpi_lite/
numa.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7use crate::binary_reader::{BinaryReader, DowncastFrom};
8use crate::structures::{
9    ACPI_SRAT_FLAG_ENABLED, ACPI_SRAT_TYPE_MEMORY_AFFINITY, ACPI_SRAT_TYPE_PROCESSOR_AFFINITY,
10    ACPI_SRAT_TYPE_PROCESSOR_X2APIC_AFFINITY, AcpiSratMemoryAffinityEntry,
11    AcpiSratProcessorAffinityEntry, AcpiSratProcessorX2ApicAffinityEntry, AcpiSratTable,
12    AcpiSubTableHeader,
13};
14use crate::{AcpiParserInterface, get_table_by_type};
15use zx_status::Status;
16
17pub const K_ACPI_MAX_NUMA_REGIONS: usize = 5;
18
19#[repr(C)]
20#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
21// A region of memory associated with a NUMA domain.
22pub struct AcpiNumaRegion {
23    pub base_address: u64,
24    pub length: u64,
25}
26
27#[repr(C)]
28#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
29// A NUMA domain.
30pub struct AcpiNumaDomain {
31    pub domain: u32,
32    pub memory: [AcpiNumaRegion; K_ACPI_MAX_NUMA_REGIONS],
33    pub memory_count: u8,
34}
35
36impl_downcast_from!(AcpiSubTableHeader =>
37    AcpiSratMemoryAffinityEntry,
38    AcpiSratProcessorAffinityEntry,
39    AcpiSratProcessorX2ApicAffinityEntry,
40);
41
42// Calls the given callback on all pairs of CPU APIC ID and NumaRegion.
43pub fn enumerate_cpu_numa_pairs_from_srat<F>(
44    srat: &AcpiSratTable,
45    mut callback: F,
46) -> Result<(), Status>
47where
48    F: FnMut(&AcpiNumaDomain, u32),
49{
50    const K_MAX_NUMA_DOMAINS: usize = 10;
51    let mut domains = [AcpiNumaDomain::default(); K_MAX_NUMA_DOMAINS];
52    for (i, domain) in domains.iter_mut().enumerate() {
53        domain.domain = i as u32;
54    }
55
56    // First find all NUMA domains.
57    let mut reader = BinaryReader::from_payload_of_struct(srat);
58    while !reader.is_empty() {
59        let sub_header = reader.read::<AcpiSubTableHeader>().ok_or(Status::INTERNAL)?;
60        if sub_header.r#type != ACPI_SRAT_TYPE_MEMORY_AFFINITY {
61            continue;
62        }
63        // SAFETY: We verified that `sub_header.r#type` is `ACPI_SRAT_TYPE_MEMORY_AFFINITY`.
64        let mem = unsafe { AcpiSratMemoryAffinityEntry::downcast_from(sub_header) }
65            .ok_or(Status::INTERNAL)?;
66
67        let flags = mem.flags;
68        if (flags & ACPI_SRAT_FLAG_ENABLED) == 0 {
69            continue;
70        }
71
72        let proximity_domain = mem.proximity_domain as usize;
73        if proximity_domain >= K_MAX_NUMA_DOMAINS {
74            return Err(Status::NOT_SUPPORTED);
75        }
76
77        let domain = &mut domains[proximity_domain];
78        if domain.memory_count as usize >= K_ACPI_MAX_NUMA_REGIONS {
79            return Err(Status::NOT_SUPPORTED);
80        }
81
82        let base_low = mem.base_address_low as u64;
83        let base_high = mem.base_address_high as u64;
84        let length_low = mem.length_low as u64;
85        let length_high = mem.length_high as u64;
86        let base = (base_high << 32) | base_low;
87        let length = (length_high << 32) | length_low;
88
89        domain.memory[domain.memory_count as usize] = AcpiNumaRegion { base_address: base, length };
90        domain.memory_count += 1;
91    }
92
93    // Then visit all CPU APIC IDs and provide the accompanying NUMA region.
94    reader = BinaryReader::from_payload_of_struct(srat);
95    while !reader.is_empty() {
96        let sub_header = reader.read::<AcpiSubTableHeader>().ok_or(Status::INTERNAL)?;
97        if sub_header.r#type == ACPI_SRAT_TYPE_PROCESSOR_AFFINITY {
98            // SAFETY: We verified that `sub_header.r#type` is `ACPI_SRAT_TYPE_PROCESSOR_AFFINITY`.
99            let cpu = unsafe { AcpiSratProcessorAffinityEntry::downcast_from(sub_header) }
100                .ok_or(Status::INTERNAL)?;
101
102            let flags = cpu.flags;
103            if (flags & ACPI_SRAT_FLAG_ENABLED) == 0 {
104                continue;
105            }
106
107            let domain = cpu.proximity_domain() as usize;
108            if domain >= K_MAX_NUMA_DOMAINS {
109                return Err(Status::INTERNAL);
110            }
111
112            let apic_id = cpu.apic_id as u32;
113            callback(&domains[domain], apic_id);
114        } else if sub_header.r#type == ACPI_SRAT_TYPE_PROCESSOR_X2APIC_AFFINITY {
115            // SAFETY: We verified that `sub_header.r#type` is `ACPI_SRAT_TYPE_PROCESSOR_X2APIC_AFFINITY`.
116            let cpu = unsafe { AcpiSratProcessorX2ApicAffinityEntry::downcast_from(sub_header) }
117                .ok_or(Status::INTERNAL)?;
118
119            let flags = cpu.flags;
120            if (flags & ACPI_SRAT_FLAG_ENABLED) == 0 {
121                continue;
122            }
123
124            let domain = cpu.proximity_domain as usize;
125            if domain >= K_MAX_NUMA_DOMAINS {
126                return Err(Status::INTERNAL);
127            }
128
129            let x2apic_id = cpu.x2apic_id;
130            callback(&domains[domain], x2apic_id);
131        }
132    }
133
134    Ok(())
135}
136
137// Calls the given callback on all pairs of CPU APIC ID and NumaRegion.
138pub fn enumerate_cpu_numa_pairs<F>(
139    parser: &dyn AcpiParserInterface,
140    callback: F,
141) -> Result<(), Status>
142where
143    F: FnMut(&AcpiNumaDomain, u32),
144{
145    let srat = get_table_by_type::<AcpiSratTable>(parser).ok_or(Status::NOT_FOUND)?;
146    enumerate_cpu_numa_pairs_from_srat(srat, callback)
147}