acpi/
mcfg.rs

1use crate::{
2    sdt::{SdtHeader, Signature},
3    AcpiTable,
4};
5use core::{mem, slice};
6
7/// Describes a set of regions of physical memory used to access the PCIe configuration space. A
8/// region is created for each entry in the MCFG. Given the segment group, bus, device number, and
9/// function of a PCIe device, the `physical_address` method on this will give you the physical
10/// address of the start of that device function's configuration space (each function has 4096
11/// bytes of configuration space in PCIe).
12#[cfg(feature = "allocator_api")]
13pub struct PciConfigRegions<'a, A>
14where
15    A: core::alloc::Allocator,
16{
17    regions: crate::ManagedSlice<'a, McfgEntry, A>,
18}
19
20#[cfg(feature = "alloc")]
21impl<'a> PciConfigRegions<'a, alloc::alloc::Global> {
22    pub fn new<H>(tables: &crate::AcpiTables<H>) -> crate::AcpiResult<PciConfigRegions<'a, alloc::alloc::Global>>
23    where
24        H: crate::AcpiHandler,
25    {
26        Self::new_in(tables, alloc::alloc::Global)
27    }
28}
29
30#[cfg(feature = "allocator_api")]
31impl<'a, A> PciConfigRegions<'a, A>
32where
33    A: core::alloc::Allocator,
34{
35    pub fn new_in<H>(tables: &crate::AcpiTables<H>, allocator: A) -> crate::AcpiResult<PciConfigRegions<'a, A>>
36    where
37        H: crate::AcpiHandler,
38    {
39        let mcfg = tables.find_table::<Mcfg>()?;
40        let mcfg_entries = mcfg.entries();
41
42        let mut regions = crate::ManagedSlice::new_in(mcfg_entries.len(), allocator)?;
43        regions.copy_from_slice(mcfg_entries);
44
45        Ok(Self { regions })
46    }
47
48    /// Get the physical address of the start of the configuration space for a given PCIe device
49    /// function. Returns `None` if there isn't an entry in the MCFG that manages that device.
50    pub fn physical_address(&self, segment_group_no: u16, bus: u8, device: u8, function: u8) -> Option<u64> {
51        // First, find the memory region that handles this segment and bus. This method is fine
52        // because there should only be one region that handles each segment group + bus
53        // combination.
54        let region = self.regions.iter().find(|region| {
55            region.pci_segment_group == segment_group_no
56                && (region.bus_number_start..=region.bus_number_end).contains(&bus)
57        })?;
58
59        Some(
60            region.base_address
61                + ((u64::from(bus - region.bus_number_start) << 20)
62                    | (u64::from(device) << 15)
63                    | (u64::from(function) << 12)),
64        )
65    }
66
67    /// Returns an iterator providing information about the system's present PCI busses.
68    /// This is roughly equivalent to manually iterating the system's MCFG table.
69    pub fn iter(&self) -> PciConfigEntryIterator {
70        PciConfigEntryIterator { entries: &self.regions, index: 0 }
71    }
72}
73
74/// Configuration entry describing a valid bus range for the given PCI segment group.
75pub struct PciConfigEntry {
76    pub segment_group: u16,
77    pub bus_range: core::ops::RangeInclusive<u8>,
78    pub physical_address: usize,
79}
80
81/// Iterator providing a [`PciConfigEntry`] for all of the valid bus ranges on the system.
82pub struct PciConfigEntryIterator<'a> {
83    entries: &'a [McfgEntry],
84    index: usize,
85}
86
87impl Iterator for PciConfigEntryIterator<'_> {
88    type Item = PciConfigEntry;
89
90    fn next(&mut self) -> Option<Self::Item> {
91        let entry = self.entries.get(self.index)?;
92        self.index += 1;
93
94        Some(PciConfigEntry {
95            segment_group: entry.pci_segment_group,
96            bus_range: entry.bus_number_start..=entry.bus_number_end,
97            physical_address: entry.base_address as usize,
98        })
99    }
100}
101
102#[repr(C, packed)]
103pub struct Mcfg {
104    header: SdtHeader,
105    _reserved: u64,
106    // Followed by `n` entries with format `McfgEntry`
107}
108
109/// ### Safety: Implementation properly represents a valid MCFG.
110unsafe impl AcpiTable for Mcfg {
111    const SIGNATURE: Signature = Signature::MCFG;
112
113    fn header(&self) -> &SdtHeader {
114        &self.header
115    }
116}
117
118impl Mcfg {
119    /// Returns a slice containing each of the entries in the MCFG table. Where possible, `PlatformInfo.interrupt_model` should
120    /// be enumerated instead.
121    pub fn entries(&self) -> &[McfgEntry] {
122        let length = self.header.length as usize - mem::size_of::<Mcfg>();
123
124        // Intentionally round down in case length isn't an exact multiple of McfgEntry size
125        // (see rust-osdev/acpi#58)
126        let num_entries = length / mem::size_of::<McfgEntry>();
127
128        unsafe {
129            let pointer = (self as *const Mcfg as *const u8).add(mem::size_of::<Mcfg>()) as *const McfgEntry;
130            slice::from_raw_parts(pointer, num_entries)
131        }
132    }
133}
134
135impl core::fmt::Debug for Mcfg {
136    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
137        formatter.debug_struct("Mcfg").field("header", &self.header).field("entries", &self.entries()).finish()
138    }
139}
140
141#[derive(Clone, Copy, Debug)]
142#[repr(C, packed)]
143pub struct McfgEntry {
144    pub base_address: u64,
145    pub pci_segment_group: u16,
146    pub bus_number_start: u8,
147    pub bus_number_end: u8,
148    _reserved: u32,
149}