Skip to content

Commit

Permalink
Adds enumerator for FreeBSD based on /dev/pci ioctl operations (RO)
Browse files Browse the repository at this point in the history
  • Loading branch information
xanathar committed May 1, 2024
1 parent 1732c19 commit 2c4bce6
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 1 deletion.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ description = "A crate to enumerate PCI devices on desktop operating systems and
[dev-dependencies]
paste = "1.0"

[target.'cfg(target_os = "freebsd")'.dependencies]
libc = "0.2"

[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.9.4"

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ Properties provided for PCI devices varies among enumerators.

Enumerator | Platforms | PCI id | PCI location | Revision | Device class | PCI subsystem | Assigned IRQ | OS driver
---------------------------------------------- | --------- | ------ | ------------ | -------- | ------------ | ----------------- | ------------ | ----------
FreeBsdDevPciEnumerator | FreeBSD | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅
LinuxProcFsPciEnumerator(Fastest) | Linux | ✅ | ✅<sup>2</sup> | ❌ | ❌ | ❌ | ✅ | ✅
LinuxProcFsPciEnumerator(HeadersOnly) | Linux | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ❌ | ❌
LinuxProcFsPciEnumerator(SkipNoncommonHeaders) | Linux | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ❌ | ✅ | ✅
Expand Down Expand Up @@ -175,3 +176,6 @@ Crate feature | Default | Description
### 0.1.0
First version published with basic enumerators for Linux, Windows and MacOS.

### Not yet released
- Added PCI enumerator for FreeBSD

16 changes: 16 additions & 0 deletions src/enumerators/freebsd/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#[cfg(target_os = "freebsd")]
mod pcidev;

use crate::{PciEnumerator, PciInfo, PciInfoError};

/// A PCI Enumerator for FreeBSD that uses `ioctl(..., PCIOCGETCONF, ...)`
/// operations over `/dev/pci` to extract partial PCI information
pub struct FreeBsdDevPciEnumerator;

impl PciEnumerator for FreeBsdDevPciEnumerator {
fn enumerate_pci(self) -> Result<PciInfo, PciInfoError> {
unsafe { pcidev::enumerate_devices() }
}
}

test_enumerator!(FreeBsdDevPciEnumerator, FreeBsdDevPciEnumerator);
182 changes: 182 additions & 0 deletions src/enumerators/freebsd/pcidev.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// See https://man.freebsd.org/cgi/man.cgi?query=pci&sektion=4&manpath=freebsd-release-ports

use std::{ffi::CStr, fs::File, io, os::fd::AsRawFd};

use crate::{
pci_device::PciDeviceProperties, pci_property_result::PropertyResult, PciDevice,
PciDeviceEnumerationError, PciDeviceEnumerationErrorImpact, PciEnumerator, PciInfo,
PciInfoError, PciLocation,
};

// The ioctl must be restarted multiple times in case it returns
// PCI_GETCONF_LIST_CHANGED. We restart a maximum of 5 (or whatever
// its written here) times.
const MAX_CHANGED_LOOPS: u32 = 5;

const PCIOCGETCONF: libc::c_ulong = 0xC0307005;

const MAX_NAME_LEN: usize = 16;

type PciGetConfStatus = u32;
const PCI_GETCONF_LAST_DEVICE: PciGetConfStatus = 0;
const PCI_GETCONF_LIST_CHANGED: PciGetConfStatus = 1;
const PCI_GETCONF_MORE_DEVS: PciGetConfStatus = 2;
const PCI_GETCONF_ERROR: PciGetConfStatus = 3;

#[allow(dead_code)]
#[repr(C)]
struct PciConfIo {
pat_buf_len: u32,
num_patterns: u32,
patterns: *const libc::c_void,
match_buf_len: u32,
num_matches: u32,
matches: *mut PciConf,
offset: u32,
generation: u32,
status: PciGetConfStatus,
}

#[derive(Default)]
#[repr(C)]
struct PciSel {
pc_domain: u32,
pc_bus: u8,
pc_dev: u8,
pc_func: u8,
}

#[derive(Default)]
#[repr(C)]
struct PciConf {
pc_sel: PciSel,
pc_hdr: u8,
pc_subvendor: u16,
pc_subdevice: u16,
pc_vendor: u16,
pc_device: u16,
pc_class: u8,
pc_subclass: u8,
pc_progif: u8,
pc_revid: u8,
pd_name: [u8; MAX_NAME_LEN + 1],
pd_unit: libc::c_long,
}

pub(super) unsafe fn enumerate_devices() -> Result<PciInfo, PciInfoError> {
let file = File::open("/dev/pci")?;
let devpci_file = file.as_raw_fd();

for _ in 0..MAX_CHANGED_LOOPS {
let mut offset = 0;
let mut pci_info = PciInfo::empty();
let mut dev = PciConf::default();

// not documented (afaik) but the API expects the object not to
// move in memory, so we init it outside the inner loop
let mut pc = PciConfIo {
pat_buf_len: 0,
num_patterns: 0,
patterns: std::ptr::null(),
match_buf_len: std::mem::size_of::<PciConf>() as u32,
num_matches: 1,
matches: &mut dev as *mut _,
offset: 0,
generation: 0,
status: PCI_GETCONF_LAST_DEVICE,
};

loop {
match libc::ioctl(devpci_file, PCIOCGETCONF, &mut pc as *mut _) {
libc::EBADF | libc::ENOTTY | libc::EFAULT => {
return Err(PciInfoError::IoError(Box::new(io::ErrorKind::InvalidData)))
}
libc::EINVAL => {
return Err(PciInfoError::IoError(Box::new(io::ErrorKind::Unsupported)))
}
err if err < 0 => {
return Err(PciInfoError::IoError(Box::new(io::ErrorKind::Other)))
}
_ => (),
}

if pc.status == PCI_GETCONF_LIST_CHANGED {
break;
}

if pc.status == PCI_GETCONF_ERROR {
return Err(PciInfoError::EnumerationInterrupted(
"enumeration interrupted with PCI_GETCONF_ERROR".into(),
));
}

if pc.num_matches == 0 {
if pci_info.results.is_empty() {
return Err(PciInfoError::EnumerationInterrupted(
"enumeration interrupted with no matches".into(),
));
} else {
pci_info.push_error(PciDeviceEnumerationError::new(
PciDeviceEnumerationErrorImpact::Bus,
PciInfoError::EnumerationInterrupted(
"enumeration interrupted with no matches".into(),
),
))
}
}

if dev.pc_vendor == 0 {
pci_info.push_error(PciDeviceEnumerationError::new(
PciDeviceEnumerationErrorImpact::Device,
PciInfoError::ValueNotFound(Some("pc_vendor".into())),
));
} else if dev.pc_device == 0 {
pci_info.push_error(PciDeviceEnumerationError::new(
PciDeviceEnumerationErrorImpact::Device,
PciInfoError::ValueNotFound(Some("pc_device".into())),
));
} else {
let (sub_v, sub_d) = if dev.pc_subvendor != 0 && dev.pc_subdevice != 0 {
(Some(dev.pc_subvendor), Some(dev.pc_subdevice))
} else {
(None, None)
};

dev.pd_name[MAX_NAME_LEN] = 0;
let name = CStr::from_bytes_until_nul(&dev.pd_name).unwrap();
let name = name.to_string_lossy().into_owned();

pci_info.push_device(PciDevice::new(
dev.pc_vendor,
dev.pc_device,
PciDeviceProperties {
location: PropertyResult::with_res(PciLocation::with_segment(
(dev.pc_sel.pc_domain & 0xFFFF) as u16,
dev.pc_sel.pc_bus,
dev.pc_sel.pc_dev,
dev.pc_sel.pc_func,
)),

subsystem_vendor_id: PropertyResult::with_val(sub_v),
subsystem_device_id: PropertyResult::with_val(sub_d),
revision: PropertyResult::with_val(dev.pc_revid),
device_class: PropertyResult::with_val(dev.pc_class),
device_subclass: PropertyResult::with_val(dev.pc_subclass),
device_iface: PropertyResult::with_val(dev.pc_progif),
os_driver: PropertyResult::with_val(Some(name)),
..Default::default()
},
));
}

offset += pc.num_matches;
pc.offset = offset;

if pc.status != PCI_GETCONF_MORE_DEVS {
return Ok(pci_info);
}
}
}

Err(PciInfoError::DevicesChangedTooManyTimes)
}
16 changes: 15 additions & 1 deletion src/enumerators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//!
//! Enumerator | Platforms | PCI id | PCI location | Revision | Device class | PCI subsystem | Assigned IRQ | OS driver
//! ---------------------------------------------- | --------- | ------ | ------------ | -------- | ------------ | ----------------- | ------------ | ----------
//! [`FreeBsdDevPciEnumerator`] | FreeBSD | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅
//! [`LinuxProcFsPciEnumerator::Fastest`] | Linux | ✅ | ✅<sup>2</sup> | ❌ | ❌ | ❌ | ✅ | ✅
//! [`LinuxProcFsPciEnumerator::HeadersOnly`] | Linux | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ✅ | ❌ | ❌
//! [`LinuxProcFsPciEnumerator::SkipNoncommonHeaders`] | Linux | ✅ | ✅<sup>2</sup> | ✅ | ✅ | ❌ | ✅ | ✅
Expand Down Expand Up @@ -36,6 +37,11 @@ mod linux;
#[cfg(any(doc, target_os = "linux"))]
pub use linux::*;

#[cfg(any(doc, target_os = "freebsd"))]
mod freebsd;
#[cfg(any(doc, target_os = "freebsd"))]
pub use freebsd::*;

/// A trait that is implemented by all types able to enumerate PCI
/// devices.
pub trait PciEnumerator {
Expand All @@ -52,10 +58,18 @@ pub fn default_pci_enumerator() -> Result<impl PciEnumerator, PciInfoError> {
#[cfg(target_os = "macos")]
return Ok(MacOsIoKitPciEnumerator);

#[cfg(target_os = "freebsd")]
return Ok(FreeBsdDevPciEnumerator);

#[cfg(target_os = "linux")]
return Ok(LinuxProcFsPciEnumerator::Exhaustive);

#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
#[cfg(not(any(
target_os = "macos",
target_os = "linux",
target_os = "windows",
target_os = "freebsd"
)))]
Err::<InvalidPciEnumerator, PciInfoError>(PciInfoError::NoDefaultPciEnumeratorForPlatform)
}

Expand Down
12 changes: 12 additions & 0 deletions src/error/pci_info_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ pub enum PciInfoError {
/// proper implementation for a default enumerator (yet).
NoDefaultPciEnumeratorForPlatform,

/// The enumeration has been interrupted by an otherwise non
/// specified error.
EnumerationInterrupted(PciInfoErrorString),

/// An error occurred parsing data. For example it may trigger if
/// an invalid string like "hello" was found where an hexadecimal
/// number was expected. The [`PciInfoErrorString`] contained in the
Expand Down Expand Up @@ -67,6 +71,10 @@ pub enum PciInfoError {
/// Parsing of an unknown PCI specialized header type has been
/// attempted.
UnknownPciHeaderType(u8),

/// The enumeration has been retried because the device list changed
/// and the maximum number of iterations has been exceeded.
DevicesChangedTooManyTimes,
}

impl Display for PciInfoError {
Expand Down Expand Up @@ -97,6 +105,10 @@ impl Display for PciInfoError {
write!(f, "this platform does not support a default PCI enumerator")
}
UnknownPciHeaderType(h) => write!(f, "unknown PCI header type 0x{h:02X}"),
EnumerationInterrupted(e) => write!(f, "the enumeration has been interrupted: {e}"),
DevicesChangedTooManyTimes => {
write!(f, "the list of PCI devices changed too many times")
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions tools/check_readyness.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ TARGETS=( \
'x86_64-pc-windows-gnu' \
'x86_64-unknown-linux-gnu' \
'x86_64-unknown-linux-musl' \
'x86_64-unknown-freebsd' \
'aarch64-apple-darwin' \
'aarch64-unknown-linux-gnu' \
'i686-pc-windows-gnu' \
'i686-unknown-linux-gnu' \
'i686-unknown-linux-gnu' \
'i686-unknown-freebsd' \
)

export RUSTFLAGS="-D warnings"
Expand Down

0 comments on commit 2c4bce6

Please sign in to comment.