277 lines
7.4 KiB
Rust

//! PCI/PCIe bus interfaces
use core::fmt;
use acpi_lib::mcfg::McfgEntry;
use alloc::{rc::Rc, vec::Vec};
use bitflags::bitflags;
use device_api::Device;
use yggdrasil_abi::error::Error;
mod space;
pub use space::{
ecam::PciEcam, PciConfigSpace, PciConfigurationSpace, PciLegacyConfigurationSpace,
};
use crate::{
mem::{address::FromRaw, PhysicalAddress},
sync::IrqSafeSpinlock,
};
bitflags! {
/// Command register of the PCI configuration space
pub struct PciCommandRegister: u16 {
/// If set, I/O access to the device is enabled
const ENABLE_IO = 1 << 0;
/// If set, memory-mapped access to the device is enabled
const ENABLE_MEMORY = 1 << 1;
/// If set, the device can generate PCI bus accesses on its own
const BUS_MASTER = 1 << 2;
/// If set, interrupts are masked from being raised
const DISABLE_INTERRUPTS = 1 << 10;
}
}
/// Represents the address of a single object on a bus (or the bus itself)
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct PciAddress {
/// PCIe segment group, ignored (?) with PCI
pub segment: u8,
/// Bus number
pub bus: u8,
/// Slot/device number
pub device: u8,
/// Function number
pub function: u8,
}
/// Address provided by PCI configuration space Base Address Register
#[derive(Debug)]
pub enum PciBaseAddress {
/// 32/64-bit memory address
Memory(usize),
/// I/O space address
Io(u16),
}
/// Describes a PCI device
#[derive(Debug)]
pub struct PciDeviceInfo {
/// Address of the device
pub address: PciAddress,
/// Configuration space access method
pub config_space: PciConfigSpace,
}
/// Interface for "taking" PCI devices from the bus
pub trait FromPciBus: Sized {
/// Constructs an instance of a driver for the device using the information provided
fn from_pci_bus(info: &PciDeviceInfo) -> Result<Self, Error>;
}
/// Used to store PCI bus devices which were enumerated by the kernel
pub struct PciBusDevice {
info: PciDeviceInfo,
driver: Option<Rc<dyn Device>>,
}
/// Represents a single PCIe bus segment
pub struct PciBusSegment {
segment_number: u8,
bus_number_start: u8,
bus_number_end: u8,
ecam_phys_base: Option<PhysicalAddress>,
devices: Vec<PciBusDevice>,
}
/// Manager struct to store and control all PCI devices in the system
pub struct PciBusManager {
segments: Vec<PciBusSegment>,
}
impl PciBusSegment {
fn probe_config_space(&self, address: PciAddress) -> Result<Option<PciConfigSpace>, Error> {
match self.ecam_phys_base {
Some(ecam_phys_base) => Ok(unsafe {
PciEcam::probe_raw_parts(ecam_phys_base, self.bus_number_start, address)?
}
.map(PciConfigSpace::Ecam)),
None => todo!(),
}
}
fn enumerate_function(
&mut self,
parent: &mut PciBusManager,
address: PciAddress,
) -> Result<(), Error> {
let Some(config) = self.probe_config_space(address)? else {
return Ok(());
};
let header_type = config.header_type();
// Enumerate multi-function devices
if address.function == 0 && header_type & 0x80 != 0 {
for function in 1..8 {
self.enumerate_function(parent, address.with_function(function))?;
}
}
// PCI-to-PCI bridge
// if config.class_code() == 0x06 && config.subclass() == 0x04 {
// let secondary_bus = config.secondary_bus();
// // TODO
// }
let info = PciDeviceInfo {
address,
config_space: config,
};
self.devices.push(PciBusDevice { info, driver: None });
Ok(())
}
fn enumerate_bus(&mut self, parent: &mut PciBusManager, bus: u8) -> Result<(), Error> {
let address = PciAddress::for_bus(self.segment_number, bus);
for i in 0..32 {
let device_address = address.with_device(i);
self.enumerate_function(parent, device_address)?;
}
Ok(())
}
/// Enumerates the bus segment, placing found devices into the manager
pub fn enumerate(&mut self, parent: &mut PciBusManager) -> Result<(), Error> {
for bus in self.bus_number_start..self.bus_number_end {
self.enumerate_bus(parent, bus)?;
}
Ok(())
}
}
impl PciBusManager {
const fn new() -> Self {
Self {
segments: Vec::new(),
}
}
/// Walks the bus device list and calls init/init_irq functions on any devices with associated
/// drivers
pub fn setup_bus_devices() -> Result<(), Error> {
Self::walk_bus_devices(|device| {
setup_bus_device(device)?;
Ok(true)
})
}
/// Iterates over the bus devices, calling the function on each of them until either an error
/// or `Ok(false)` is returned
pub fn walk_bus_devices<F: FnMut(&mut PciBusDevice) -> Result<bool, Error>>(
mut f: F,
) -> Result<(), Error> {
let mut this = PCI_MANAGER.lock();
for segment in this.segments.iter_mut() {
for device in segment.devices.iter_mut() {
if !f(device)? {
return Ok(());
}
}
}
Ok(())
}
/// Enumerates a bus segment provided by ACPI MCFG table entry
pub fn add_segment_from_mcfg(entry: &McfgEntry) -> Result<(), Error> {
let mut bus_segment = PciBusSegment {
segment_number: entry.pci_segment_group as u8,
bus_number_start: entry.bus_number_start,
bus_number_end: entry.bus_number_end,
ecam_phys_base: Some(PhysicalAddress::from_raw(entry.base_address)),
devices: Vec::new(),
};
let mut this = PCI_MANAGER.lock();
bus_segment.enumerate(&mut *this)?;
this.segments.push(bus_segment);
Ok(())
}
}
impl fmt::Display for PciAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}:{}", self.bus, self.device, self.function)
}
}
impl PciAddress {
/// Constructs a [PciAddress] representing a bus
pub const fn for_bus(segment: u8, bus: u8) -> Self {
Self {
segment,
bus,
device: 0,
function: 0,
}
}
/// Constructs a [PciAddress] representing a device on a given bus
pub const fn with_device(self, device: u8) -> Self {
Self {
device,
function: 0,
..self
}
}
/// Constructs a [PciAddress] representing a function of a given bus device
pub const fn with_function(self, function: u8) -> Self {
Self { function, ..self }
}
}
impl PciConfigurationSpace for PciConfigSpace {
fn read_u32(&self, offset: usize) -> u32 {
match self {
Self::Ecam(ecam) => ecam.read_u32(offset),
_ => todo!(),
}
}
}
fn setup_bus_device(device: &mut PciBusDevice) -> Result<(), Error> {
if device.driver.is_some() {
return Ok(());
}
let config = &device.info.config_space;
debugln!(
"{}: {:04x}:{:04x}",
device.info.address,
config.vendor_id(),
config.device_id()
);
// Match by class
match (config.class_code(), config.subclass(), config.prog_if()) {
_ => {
debugln!(" -> No driver");
}
}
Ok(())
}
static PCI_MANAGER: IrqSafeSpinlock<PciBusManager> = IrqSafeSpinlock::new(PciBusManager::new());