فهرست منبع

Implement iterator to enumerate PCI bus.

Enumerate PCI/PCIe buses in aarch64 example.
Andrew Walbran 2 سال پیش
والد
کامیت
be0cfeedcc
3فایلهای تغییر یافته به همراه222 افزوده شده و 1 حذف شده
  1. 27 1
      examples/aarch64/src/main.rs
  2. 2 0
      src/transport/pci.rs
  3. 193 0
      src/transport/pci/bus.rs

+ 27 - 1
examples/aarch64/src/main.rs

@@ -7,11 +7,12 @@ mod logger;
 mod pl011;
 
 use core::{mem::size_of, panic::PanicInfo, ptr::NonNull};
-use fdt::{standard_nodes::Compatible, Fdt};
+use fdt::{node::FdtNode, standard_nodes::Compatible, Fdt};
 use hal::HalImpl;
 use log::{debug, error, info, trace, warn, LevelFilter};
 use psci::system_off;
 use virtio_drivers::{
+    pci::bus::{Cam, PciRoot},
     DeviceType, MmioTransport, Transport, VirtIOBlk, VirtIOGpu, VirtIOHeader, VirtIONet,
 };
 
@@ -71,6 +72,15 @@ extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) {
         }
     }
 
+    if let Some(pci_node) = fdt.find_compatible(&["pci-host-cam-generic"]) {
+        info!("Found PCI node: {}", pci_node.name);
+        enumerate_pci(pci_node, Cam::MmioCam);
+    }
+    if let Some(pcie_node) = fdt.find_compatible(&["pci-host-ecam-generic"]) {
+        info!("Found PCIe node: {}", pcie_node.name);
+        enumerate_pci(pcie_node, Cam::Ecam);
+    }
+
     system_off().unwrap();
 }
 
@@ -126,6 +136,22 @@ fn virtio_net<T: Transport>(transport: T) {
     info!("virtio-net test finished");
 }
 
+fn enumerate_pci(pci_node: FdtNode, cam: Cam) {
+    let reg = pci_node.reg().expect("PCI node missing reg property.");
+    for region in reg {
+        info!(
+            "Reg: {:?}-{:#x}",
+            region.starting_address,
+            region.starting_address as usize + region.size.unwrap()
+        );
+        // Safe because we know the pointer is to a valid MMIO region.
+        let mut pci_root = unsafe { PciRoot::new(region.starting_address as *mut u8, cam) };
+        for (device_function, info) in pci_root.enumerate_bus(0) {
+            info!("Found {} at {}", info, device_function);
+        }
+    }
+}
+
 #[panic_handler]
 fn panic(info: &PanicInfo) -> ! {
     error!("{}", info);

+ 2 - 0
src/transport/pci.rs

@@ -1,5 +1,7 @@
 //! PCI transport for VirtIO.
 
+pub mod bus;
+
 use super::DeviceType;
 
 /// The PCI vendor ID for VirtIO devices.

+ 193 - 0
src/transport/pci/bus.rs

@@ -0,0 +1,193 @@
+//! Module for dealing with a PCI bus in general, without anything specific to VirtIO.
+
+use core::fmt::{self, Display, Formatter};
+
+const INVALID_READ: u32 = 0xffffffff;
+// PCI MMIO configuration region size.
+const AARCH64_PCI_CFG_SIZE: u32 = 0x1000000;
+// PCIe MMIO configuration region size.
+const AARCH64_PCIE_CFG_SIZE: u32 = 0x10000000;
+
+/// The maximum number of devices on a bus.
+const MAX_DEVICES: u8 = 32;
+/// The maximum number of functions on a device.
+const MAX_FUNCTIONS: u8 = 8;
+
+/// The root complex of a PCI bus.
+#[derive(Clone, Debug)]
+pub struct PciRoot {
+    mmio_base: *mut u32,
+    cam: Cam,
+}
+
+/// A PCI Configuration Access Mechanism.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum Cam {
+    /// The PCI memory-mapped Configuration Access Mechanism.
+    ///
+    /// This provides access to 256 bytes of configuration space per device function.
+    MmioCam,
+    /// The PCIe memory-mapped Enhanced Configuration Access Mechanism.
+    ///
+    /// This provides access to 4 KiB of configuration space per device function.
+    Ecam,
+}
+
+impl PciRoot {
+    /// Wraps the PCI root complex with the given MMIO base address.
+    ///
+    /// Panics if the base address is not aligned to a 4-byte boundary.
+    ///
+    /// # Safety
+    ///
+    /// `mmio_base` must be a valid pointer to an appropriately-mapped MMIO region of at least
+    /// 16 MiB (if `cam == Cam::MmioCam`) or 256 MiB (if `cam == Cam::Ecam`). The pointer must be
+    /// valid for the entire lifetime of the program (i.e. `'static`), which implies that no Rust
+    /// references may be used to access any of the memory region at any point.
+    pub unsafe fn new(mmio_base: *mut u8, cam: Cam) -> Self {
+        assert!(mmio_base as usize & 0x3 == 0);
+        Self {
+            mmio_base: mmio_base as *mut u32,
+            cam,
+        }
+    }
+
+    fn cam_offset(&self, device_function: DeviceFunction, register_offset: u8) -> u32 {
+        let bdf = (device_function.bus as u32) << 8
+            | (device_function.device as u32) << 3
+            | device_function.function as u32;
+        let address;
+        match self.cam {
+            Cam::MmioCam => {
+                address = bdf << 8 | register_offset as u32;
+                // Ensure that address is within range.
+                // TODO: Return an error rather than panicking?
+                assert!(address < AARCH64_PCI_CFG_SIZE);
+            }
+            Cam::Ecam => {
+                address = bdf << 12 | register_offset as u32;
+                // Ensure that address is within range.
+                // TODO: Return an error rather than panicking?
+                assert!(address < AARCH64_PCIE_CFG_SIZE);
+            }
+        }
+        // Ensure that address is word-aligned.
+        assert!(address & 0x3 == 0);
+        address
+    }
+
+    /// Reads 4 bytes from configuration space using the appropriate CAM.
+    fn config_read_word(&self, device_function: DeviceFunction, register_offset: u8) -> u32 {
+        let address = self.cam_offset(device_function, register_offset);
+        // Safe because both the `mmio_base` and the address offset are properly aligned, and the
+        // resulting pointer is within the MMIO range of the CAM.
+        unsafe {
+            // Right shift to convert from byte offset to word offset.
+            (self.mmio_base.add((address >> 2) as usize)).read_volatile()
+        }
+    }
+
+    /// Enumerates PCI devices on the given bus.
+    pub fn enumerate_bus(&self, bus: u8) -> BusDeviceIterator {
+        BusDeviceIterator {
+            root: self.clone(),
+            next: DeviceFunction {
+                bus,
+                device: 0,
+                function: 0,
+            },
+        }
+    }
+}
+
+/// An iterator which enumerates PCI devices and functions on a given bus.
+#[derive(Debug)]
+pub struct BusDeviceIterator {
+    root: PciRoot,
+    next: DeviceFunction,
+}
+
+impl Iterator for BusDeviceIterator {
+    type Item = (DeviceFunction, DeviceFunctionInfo);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        while self.next.device < MAX_DEVICES {
+            // Read the header for the current device and function.
+            let current = self.next;
+            let device_vendor = self.root.config_read_word(current, 0);
+
+            // Advance to the next device or function.
+            self.next.function += 1;
+            if self.next.function >= MAX_FUNCTIONS {
+                self.next.function = 0;
+                self.next.device += 1;
+            }
+
+            if device_vendor != INVALID_READ {
+                let class_revision = self.root.config_read_word(current, 8);
+                let device_id = (device_vendor >> 16) as u16;
+                let vendor_id = device_vendor as u16;
+                let class = (class_revision >> 24) as u8;
+                let subclass = (class_revision >> 16) as u8;
+                let prog_if = (class_revision >> 8) as u8;
+                let revision = class_revision as u8;
+                return Some((
+                    current,
+                    DeviceFunctionInfo {
+                        vendor_id,
+                        device_id,
+                        class,
+                        subclass,
+                        prog_if,
+                        revision,
+                    },
+                ));
+            }
+        }
+        None
+    }
+}
+
+/// An identifier for a PCI bus, device and function.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct DeviceFunction {
+    /// The PCI bus number, between 0 and 255.
+    pub bus: u8,
+    /// The device number on the bus, between 0 and 31.
+    pub device: u8,
+    /// The function number of the device, between 0 and 7.
+    pub function: u8,
+}
+
+impl Display for DeviceFunction {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        write!(f, "{:02x}:{:02x}.{}", self.bus, self.device, self.function)
+    }
+}
+
+/// Information about a PCI device function.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct DeviceFunctionInfo {
+    /// The PCI vendor ID.
+    pub vendor_id: u16,
+    /// The PCI device ID.
+    pub device_id: u16,
+    /// The PCI class.
+    pub class: u8,
+    /// The PCI subclass.
+    pub subclass: u8,
+    /// The PCI programming interface byte.
+    pub prog_if: u8,
+    /// The PCI revision ID.
+    pub revision: u8,
+}
+
+impl Display for DeviceFunctionInfo {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        write!(
+            f,
+            "{:04x}:{:04x} (class {:02x}.{:02x}, rev {:02x})",
+            self.vendor_id, self.device_id, self.class, self.subclass, self.revision
+        )
+    }
+}