diff --git a/Cargo.toml b/Cargo.toml
index 4161d22..dac4d14 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/README.md b/README.md
index 664cd59..85ab35a 100644
--- a/README.md
+++ b/README.md
@@ -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 | ✅ | ✅2 | ❌ | ❌ | ❌ | ✅ | ✅
LinuxProcFsPciEnumerator(HeadersOnly) | Linux | ✅ | ✅2 | ✅ | ✅ | ✅ | ❌ | ❌
LinuxProcFsPciEnumerator(SkipNoncommonHeaders) | Linux | ✅ | ✅2 | ✅ | ✅ | ❌ | ✅ | ✅
@@ -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
+
diff --git a/src/enumerators/freebsd/mod.rs b/src/enumerators/freebsd/mod.rs
new file mode 100644
index 0000000..c70e240
--- /dev/null
+++ b/src/enumerators/freebsd/mod.rs
@@ -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 {
+ unsafe { pcidev::enumerate_devices() }
+ }
+}
+
+test_enumerator!(FreeBsdDevPciEnumerator, FreeBsdDevPciEnumerator);
diff --git a/src/enumerators/freebsd/pcidev.rs b/src/enumerators/freebsd/pcidev.rs
new file mode 100644
index 0000000..5d3d3b3
--- /dev/null
+++ b/src/enumerators/freebsd/pcidev.rs
@@ -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 {
+ 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::() 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)
+}
diff --git a/src/enumerators/mod.rs b/src/enumerators/mod.rs
index 254362d..8d77af6 100644
--- a/src/enumerators/mod.rs
+++ b/src/enumerators/mod.rs
@@ -6,6 +6,7 @@
//!
//! Enumerator | Platforms | PCI id | PCI location | Revision | Device class | PCI subsystem | Assigned IRQ | OS driver
//! ---------------------------------------------- | --------- | ------ | ------------ | -------- | ------------ | ----------------- | ------------ | ----------
+//! [`FreeBsdDevPciEnumerator`] | FreeBSD | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅
//! [`LinuxProcFsPciEnumerator::Fastest`] | Linux | ✅ | ✅2 | ❌ | ❌ | ❌ | ✅ | ✅
//! [`LinuxProcFsPciEnumerator::HeadersOnly`] | Linux | ✅ | ✅2 | ✅ | ✅ | ✅ | ❌ | ❌
//! [`LinuxProcFsPciEnumerator::SkipNoncommonHeaders`] | Linux | ✅ | ✅2 | ✅ | ✅ | ❌ | ✅ | ✅
@@ -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 {
@@ -52,10 +58,18 @@ pub fn default_pci_enumerator() -> Result {
#[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::(PciInfoError::NoDefaultPciEnumeratorForPlatform)
}
diff --git a/src/error/pci_info_error.rs b/src/error/pci_info_error.rs
index 976ae4a..e6017ab 100644
--- a/src/error/pci_info_error.rs
+++ b/src/error/pci_info_error.rs
@@ -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
@@ -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 {
@@ -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")
+ }
}
}
}
diff --git a/tools/check_readyness.sh b/tools/check_readyness.sh
index 4df3120..623ba32 100755
--- a/tools/check_readyness.sh
+++ b/tools/check_readyness.sh
@@ -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"