| use crate::{ |
| sdt::{SdtHeader, Signature}, |
| AcpiTable, |
| }; |
| use core::{mem, slice}; |
| |
| /// Describes a set of regions of physical memory used to access the PCIe configuration space. A |
| /// region is created for each entry in the MCFG. Given the segment group, bus, device number, and |
| /// function of a PCIe device, the `physical_address` method on this will give you the physical |
| /// address of the start of that device function's configuration space (each function has 4096 |
| /// bytes of configuration space in PCIe). |
| #[cfg(feature = "allocator_api")] |
| pub struct PciConfigRegions<'a, A> |
| where |
| A: core::alloc::Allocator, |
| { |
| regions: crate::ManagedSlice<'a, McfgEntry, A>, |
| } |
| |
| #[cfg(feature = "alloc")] |
| impl<'a> PciConfigRegions<'a, alloc::alloc::Global> { |
| pub fn new<H>(tables: &crate::AcpiTables<H>) -> crate::AcpiResult<PciConfigRegions<'a, alloc::alloc::Global>> |
| where |
| H: crate::AcpiHandler, |
| { |
| Self::new_in(tables, alloc::alloc::Global) |
| } |
| } |
| |
| #[cfg(feature = "allocator_api")] |
| impl<'a, A> PciConfigRegions<'a, A> |
| where |
| A: core::alloc::Allocator, |
| { |
| pub fn new_in<H>(tables: &crate::AcpiTables<H>, allocator: A) -> crate::AcpiResult<PciConfigRegions<'a, A>> |
| where |
| H: crate::AcpiHandler, |
| { |
| let mcfg = tables.find_table::<Mcfg>()?; |
| let mcfg_entries = mcfg.entries(); |
| |
| let mut regions = crate::ManagedSlice::new_in(mcfg_entries.len(), allocator)?; |
| regions.copy_from_slice(mcfg_entries); |
| |
| Ok(Self { regions }) |
| } |
| |
| /// Get the physical address of the start of the configuration space for a given PCIe device |
| /// function. Returns `None` if there isn't an entry in the MCFG that manages that device. |
| pub fn physical_address(&self, segment_group_no: u16, bus: u8, device: u8, function: u8) -> Option<u64> { |
| // First, find the memory region that handles this segment and bus. This method is fine |
| // because there should only be one region that handles each segment group + bus |
| // combination. |
| let region = self.regions.iter().find(|region| { |
| region.pci_segment_group == segment_group_no |
| && (region.bus_number_start..=region.bus_number_end).contains(&bus) |
| })?; |
| |
| Some( |
| region.base_address |
| + ((u64::from(bus - region.bus_number_start) << 20) |
| | (u64::from(device) << 15) |
| | (u64::from(function) << 12)), |
| ) |
| } |
| |
| /// Returns an iterator providing information about the system's present PCI busses. |
| /// This is roughly equivalent to manually iterating the system's MCFG table. |
| pub fn iter(&self) -> PciConfigEntryIterator { |
| PciConfigEntryIterator { entries: &self.regions, index: 0 } |
| } |
| } |
| |
| /// Configuration entry describing a valid bus range for the given PCI segment group. |
| pub struct PciConfigEntry { |
| pub segment_group: u16, |
| pub bus_range: core::ops::RangeInclusive<u8>, |
| pub physical_address: usize, |
| } |
| |
| /// Iterator providing a [`PciConfigEntry`] for all of the valid bus ranges on the system. |
| pub struct PciConfigEntryIterator<'a> { |
| entries: &'a [McfgEntry], |
| index: usize, |
| } |
| |
| impl Iterator for PciConfigEntryIterator<'_> { |
| type Item = PciConfigEntry; |
| |
| fn next(&mut self) -> Option<Self::Item> { |
| let entry = self.entries.get(self.index)?; |
| self.index += 1; |
| |
| Some(PciConfigEntry { |
| segment_group: entry.pci_segment_group, |
| bus_range: entry.bus_number_start..=entry.bus_number_end, |
| physical_address: entry.base_address as usize, |
| }) |
| } |
| } |
| |
| #[repr(C, packed)] |
| pub struct Mcfg { |
| header: SdtHeader, |
| _reserved: u64, |
| // Followed by `n` entries with format `McfgEntry` |
| } |
| |
| /// ### Safety: Implementation properly represents a valid MCFG. |
| unsafe impl AcpiTable for Mcfg { |
| const SIGNATURE: Signature = Signature::MCFG; |
| |
| fn header(&self) -> &SdtHeader { |
| &self.header |
| } |
| } |
| |
| impl Mcfg { |
| /// Returns a slice containing each of the entries in the MCFG table. Where possible, `PlatformInfo.interrupt_model` should |
| /// be enumerated instead. |
| pub fn entries(&self) -> &[McfgEntry] { |
| let length = self.header.length as usize - mem::size_of::<Mcfg>(); |
| |
| // Intentionally round down in case length isn't an exact multiple of McfgEntry size |
| // (see rust-osdev/acpi#58) |
| let num_entries = length / mem::size_of::<McfgEntry>(); |
| |
| unsafe { |
| let pointer = (self as *const Mcfg as *const u8).add(mem::size_of::<Mcfg>()) as *const McfgEntry; |
| slice::from_raw_parts(pointer, num_entries) |
| } |
| } |
| } |
| |
| impl core::fmt::Debug for Mcfg { |
| fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| formatter.debug_struct("Mcfg").field("header", &self.header).field("entries", &self.entries()).finish() |
| } |
| } |
| |
| #[derive(Clone, Copy, Debug)] |
| #[repr(C, packed)] |
| pub struct McfgEntry { |
| pub base_address: u64, |
| pub pci_segment_group: u16, |
| pub bus_number_start: u8, |
| pub bus_number_end: u8, |
| _reserved: u32, |
| } |