Skip to main content

strat9_kernel/acpi/
mcfg.rs

1//! Support for MCFG (PCI Express MMCONFIG) ACPI table.
2
3use super::sdt::Sdt;
4use alloc::vec::Vec;
5use zerocopy::FromBytes;
6
7pub const MCFG_SIGNATURE: &[u8; 4] = b"MCFG";
8
9#[derive(Clone, Copy, Debug, FromBytes)]
10#[repr(C, packed)]
11pub struct Mcfg {
12    pub header: Sdt,
13    pub _reserved: u64,
14}
15
16impl Mcfg {
17    /// Finds the MCFG and returns a reference to it.
18    pub fn get() -> Option<&'static Mcfg> {
19        unsafe { super::find_table(MCFG_SIGNATURE).map(|ptr| &*(ptr as *const Mcfg)) }
20    }
21}
22
23#[derive(Clone, Copy, Debug, FromBytes)]
24#[repr(C, packed)]
25struct McfgAllocation {
26    base_address: u64,
27    segment_group: u16,
28    start_bus: u8,
29    end_bus: u8,
30    _reserved: u32,
31}
32
33#[derive(Clone, Copy, Debug)]
34pub struct McfgEntry {
35    pub base_address: u64,
36    pub segment_group: u16,
37    pub start_bus: u8,
38    pub end_bus: u8,
39}
40
41impl McfgEntry {
42    /// Performs the bus count operation.
43    pub fn bus_count(&self) -> u16 {
44        (self.end_bus as u16).saturating_sub(self.start_bus as u16) + 1
45    }
46}
47
48#[derive(Debug)]
49pub struct McfgInfo {
50    pub entries: Vec<McfgEntry>,
51}
52
53impl McfgInfo {
54    /// Performs the entry for segment operation.
55    pub fn entry_for_segment(&self, segment_group: u16) -> Option<&McfgEntry> {
56        self.entries
57            .iter()
58            .find(|e| e.segment_group == segment_group)
59    }
60}
61
62/// Returns whether aligned 1m.
63fn is_aligned_1m(addr: u64) -> bool {
64    (addr & ((1 << 20) - 1)) == 0
65}
66
67/// Performs the should skip entry operation.
68fn should_skip_entry(entry: &McfgEntry) -> bool {
69    if entry.base_address == 0 {
70        return true;
71    }
72    if entry.start_bus > entry.end_bus {
73        return true;
74    }
75    if !is_aligned_1m(entry.base_address) {
76        return true;
77    }
78    false
79}
80
81/// Performs the overlaps operation.
82fn overlaps(a: &McfgEntry, b: &McfgEntry) -> bool {
83    if a.segment_group != b.segment_group {
84        return false;
85    }
86    !(a.end_bus < b.start_bus || b.end_bus < a.start_bus)
87}
88
89/// Parses mcfg.
90pub fn parse_mcfg() -> Option<McfgInfo> {
91    let mcfg_ptr = super::find_table(MCFG_SIGNATURE)? as *const Mcfg;
92    let mcfg = unsafe { &*mcfg_ptr };
93
94    let header_len = mcfg.header.length as usize;
95    let min_len = core::mem::size_of::<Mcfg>();
96    if header_len < min_len {
97        log::error!("ACPI: MCFG length too small: {}", header_len);
98        return None;
99    }
100
101    let alloc_len = core::mem::size_of::<McfgAllocation>();
102    let entries_bytes = header_len - min_len;
103    if entries_bytes % alloc_len != 0 {
104        log::warn!(
105            "ACPI: MCFG payload not aligned to allocation size (payload={}, alloc={})",
106            entries_bytes,
107            alloc_len
108        );
109    }
110
111    let mut entries = Vec::new();
112    let mut offset = mcfg_ptr as usize + min_len;
113    let end = mcfg_ptr as usize + header_len;
114
115    while offset + alloc_len <= end {
116        let alloc = unsafe { core::ptr::read_unaligned(offset as *const McfgAllocation) };
117        let candidate = McfgEntry {
118            base_address: alloc.base_address,
119            segment_group: alloc.segment_group,
120            start_bus: alloc.start_bus,
121            end_bus: alloc.end_bus,
122        };
123        if should_skip_entry(&candidate) {
124            log::warn!(
125                "ACPI: MCFG entry skipped (seg={} ecam={:#x} buses={}..{})",
126                candidate.segment_group,
127                candidate.base_address,
128                candidate.start_bus,
129                candidate.end_bus
130            );
131            offset += alloc_len;
132            continue;
133        }
134        if entries.iter().any(|e| overlaps(e, &candidate)) {
135            log::warn!(
136                "ACPI: MCFG overlapping entry skipped (seg={} ecam={:#x} buses={}..{})",
137                candidate.segment_group,
138                candidate.base_address,
139                candidate.start_bus,
140                candidate.end_bus
141            );
142            offset += alloc_len;
143            continue;
144        }
145        entries.push(candidate);
146        offset += alloc_len;
147    }
148
149    if entries.is_empty() {
150        log::warn!("ACPI: MCFG present but no usable allocation entries");
151        return None;
152    }
153
154    Some(McfgInfo { entries })
155}