use core::fmt::{Display, Formatter};
use core::mem::{self, MaybeUninit};
use alloc::collections::btree_map::BTreeMap;
use alloc::collections::btree_set::{self, BTreeSet};
use alloc::fmt;
use crate::proto::device_path::build::{BuildError, DevicePathBuilder};
use crate::proto::device_path::{self, DevicePath, DevicePathUtilitiesError, PoolDevicePath};
use super::PciIoAddress;
use super::root_bridge::PciRootBridgeIo;
#[allow(unused)]
#[derive(Clone, Copy, Debug)]
struct PciRegister0 {
vendor_id: u16,
device_id: u16,
}
#[allow(unused)]
#[derive(Clone, Copy, Debug)]
struct PciRegister2 {
revision_id: u8,
prog_if: u8,
subclass: u8,
class: u8,
}
#[allow(unused)]
#[derive(Clone, Copy, Debug)]
struct PciRegister3 {
cache_line_size: u8,
latency_timer: u8,
header_type: u8,
bist: u8,
}
#[allow(unused)]
#[derive(Clone, Copy, Debug)]
struct PciHeader1Register6 {
primary_bus: u8,
secondary_bus: u8,
subordinate_bus: u8,
secondary_latency_timer: u8,
}
fn read_device_register_u32<T: Sized + Copy>(
proto: &mut PciRootBridgeIo,
addr: PciIoAddress,
) -> uefi::Result<T> {
unsafe {
let raw = proto.pci().read_one::<u32>(addr)?;
let reg: T = mem::transmute_copy(&raw);
Ok(reg)
}
}
#[derive(Debug)]
pub enum PciDevicePathBuildError {
InvalidAddress,
PathBuildError(BuildError),
DevicePathUtilitiesError(DevicePathUtilitiesError),
}
impl From<BuildError> for PciDevicePathBuildError {
fn from(value: BuildError) -> Self {
Self::PathBuildError(value)
}
}
impl From<DevicePathUtilitiesError> for PciDevicePathBuildError {
fn from(value: DevicePathUtilitiesError) -> Self {
Self::DevicePathUtilitiesError(value)
}
}
impl Display for PciDevicePathBuildError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl core::error::Error for PciDevicePathBuildError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::PathBuildError(e) => Some(e),
Self::DevicePathUtilitiesError(e) => Some(e),
_ => None,
}
}
}
#[derive(Debug)]
pub struct PciTree {
segment: u32,
devices: BTreeSet<PciIoAddress>,
bus_anchors: BTreeMap<u8 , PciIoAddress>,
}
impl PciTree {
pub(crate) const fn new(segment: u32) -> Self {
Self {
segment,
devices: BTreeSet::new(),
bus_anchors: BTreeMap::new(),
}
}
pub(crate) fn should_visit_bus(&self, bus: u8) -> bool {
!self.bus_anchors.contains_key(&bus)
}
pub(crate) fn push_device(&mut self, addr: PciIoAddress) {
self.devices.insert(addr);
}
pub(crate) fn push_bridge(&mut self, addr: PciIoAddress, child_bus: u8) -> bool {
match self.bus_anchors.contains_key(&child_bus) {
true => false,
false => {
self.bus_anchors.insert(child_bus, addr);
true
}
}
}
pub fn iter(&self) -> btree_set::Iter<'_, PciIoAddress> {
self.devices.iter()
}
#[must_use]
pub const fn segment_nr(&self) -> u32 {
self.segment
}
#[must_use]
pub fn parent_for(&self, addr: PciIoAddress) -> Option<PciIoAddress> {
self.bus_anchors.get(&addr.bus).cloned()
}
pub fn child_bus_of_iter(&self, addr: PciIoAddress) -> impl Iterator<Item = u8> {
self.bus_anchors
.iter()
.filter(move |&(_, parent)| *parent == addr)
.map(|(bus, _)| bus)
.cloned()
}
pub fn device_path(
&self,
root_path: &DevicePath,
addr: PciIoAddress,
) -> Result<PoolDevicePath, PciDevicePathBuildError> {
use device_path::build;
if !self.devices.contains(&addr) {
return Err(PciDevicePathBuildError::InvalidAddress);
}
let mut bfr = [MaybeUninit::uninit(); 2048];
let mut builder = DevicePathBuilder::with_buf(&mut bfr);
for node in root_path.node_iter() {
builder = builder.push(&node)?;
}
fn inner<'a>(
root: &PciTree,
mut builder: DevicePathBuilder<'a>,
addr: PciIoAddress,
) -> Result<DevicePathBuilder<'a>, BuildError> {
if let Some(parent) = root.parent_for(addr) {
builder = inner(root, builder, parent)?;
}
builder.push(&build::hardware::Pci {
function: addr.fun,
device: addr.dev,
})
}
builder = inner(self, builder, addr)?;
Ok(builder.finalize()?.to_pool()?)
}
}
impl IntoIterator for PciTree {
type Item = PciIoAddress;
type IntoIter = btree_set::IntoIter<PciIoAddress>;
fn into_iter(self) -> Self::IntoIter {
self.devices.into_iter()
}
}
impl<'a> IntoIterator for &'a PciTree {
type Item = &'a PciIoAddress;
type IntoIter = btree_set::Iter<'a, PciIoAddress>;
fn into_iter(self) -> Self::IntoIter {
self.devices.iter()
}
}
fn get_vendor_id(proto: &mut PciRootBridgeIo, addr: PciIoAddress) -> uefi::Result<u16> {
read_device_register_u32::<PciRegister0>(proto, addr.with_register(0)).map(|v| v.vendor_id)
}
fn get_classes(proto: &mut PciRootBridgeIo, addr: PciIoAddress) -> uefi::Result<(u8, u8)> {
let reg = read_device_register_u32::<PciRegister2>(proto, addr.with_register(2 * 4))?;
Ok((reg.class, reg.subclass))
}
fn get_header_type(proto: &mut PciRootBridgeIo, addr: PciIoAddress) -> uefi::Result<u8> {
read_device_register_u32::<PciRegister3>(proto, addr.with_register(3 * 4))
.map(|v| v.header_type)
}
fn get_secondary_bus_range(
proto: &mut PciRootBridgeIo,
addr: PciIoAddress,
) -> uefi::Result<(u8, u8)> {
let reg = read_device_register_u32::<PciHeader1Register6>(proto, addr.with_register(6 * 4))?;
Ok((reg.secondary_bus, reg.subordinate_bus))
}
fn visit_function(
proto: &mut PciRootBridgeIo,
addr: PciIoAddress,
tree: &mut PciTree,
) -> uefi::Result<()> {
if get_vendor_id(proto, addr)? == 0xFFFF {
return Ok(()); }
tree.push_device(addr);
let (base_class, sub_class) = get_classes(proto, addr)?;
let header_type = get_header_type(proto, addr)? & 0b01111111;
if base_class == 0x6 && sub_class == 0x4 && header_type == 0x01 {
let (secondary_bus_nr, subordinate_bus_nr) = get_secondary_bus_range(proto, addr)?;
if secondary_bus_nr == 0 || subordinate_bus_nr < secondary_bus_nr {
return Ok(());
}
for bus in secondary_bus_nr..=subordinate_bus_nr {
if tree.push_bridge(addr, bus) {
visit_bus(proto, PciIoAddress::new(bus, 0, 0), tree)?;
}
}
}
Ok(())
}
fn visit_device(
proto: &mut PciRootBridgeIo,
addr: PciIoAddress,
tree: &mut PciTree,
) -> uefi::Result<()> {
if get_vendor_id(proto, addr)? == 0xFFFF {
return Ok(()); }
visit_function(proto, addr.with_function(0), tree)?;
if get_header_type(proto, addr.with_function(0))? & 0x80 != 0 {
for fun in 1..=7 {
visit_function(proto, addr.with_function(fun), tree)?;
}
}
Ok(())
}
pub(crate) fn visit_bus(
proto: &mut PciRootBridgeIo,
addr: PciIoAddress,
tree: &mut PciTree,
) -> uefi::Result<()> {
for dev in 0..32 {
visit_device(proto, addr.with_device(dev), tree)?;
}
Ok(())
}