Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds enumerator for FreeBSD based on /dev/pci ioctl operations (RO) #10

Merged
merged 1 commit into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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