mod device;
mod port;
use std::io::Error as IoError;
use regex::Regex;
use thiserror::Error;
pub(crate) use self::device::*;
pub use self::port::*;
use super::context::*;
enum PortSpeedFilter {
AtLeast(f32),
Exactly(f32),
}
pub struct NicFinder {
dev_names: Vec<Regex>,
port_nums: Vec<u8>,
port_speed: PortSpeedFilter,
port_link_layer: Option<PortLinkLayer>,
numa_nodes: Vec<u8>,
}
impl NicFinder {
fn is_device_eligible(&self, ctx: IbvContext) -> bool {
(
self.dev_names.is_empty() || {
let Ok(dev_name) = ctx.dev().name() else {
return false;
};
self.dev_names.iter().any(|re| re.is_match(&dev_name))
}
) && ({
self.port_nums.is_empty() || {
let Ok(dev_attr) = ctx.query_device() else {
return false;
};
self.port_nums
.iter()
.all(|&num| num <= dev_attr.phys_port_cnt)
}
}) && (
self.numa_nodes.is_empty() || {
let Ok(numa) = ctx.dev().numa_node() else {
return false;
};
self.numa_nodes.contains(&numa)
}
)
}
fn is_port_eligible(&self, ctx: IbvContext, port_num: u8) -> bool {
let Ok(port) = Port::new(ctx, port_num) else {
return false;
};
(
self.port_nums.is_empty() || self.port_nums.contains(&port_num)
) && (
match self.port_speed {
PortSpeedFilter::AtLeast(speed) => speed <= port.speed().gbps(),
PortSpeedFilter::Exactly(speed) => speed == port.speed().gbps(),
}
) && (
self.port_link_layer.is_none() || {
let link_layer = port.link_layer();
self.port_link_layer == Some(link_layer)
}
)
}
}
impl NicFinder {
pub fn new() -> Self {
Self {
dev_names: Vec::new(),
port_nums: Vec::new(),
port_speed: PortSpeedFilter::AtLeast(0.0),
port_link_layer: None,
numa_nodes: Vec::new(),
}
}
#[inline]
pub fn dev_name(mut self, name: impl AsRef<str>) -> Self {
self.dev_names
.push(Regex::new(name.as_ref()).expect("invalid regex pattern"));
self
}
#[inline]
pub fn port_num(mut self, num: u8) -> Self {
assert!(num > 0, "port number must be positive");
self.port_nums.push(num);
self
}
#[inline]
pub fn port_speed_at_least(mut self, speed: f32) -> Self {
self.port_speed = PortSpeedFilter::AtLeast(speed);
self
}
#[inline]
pub fn port_speed_exactly(mut self, speed: f32) -> Self {
self.port_speed = PortSpeedFilter::Exactly(speed);
self
}
#[inline]
pub fn port_link_layer(mut self, link_layer: PortLinkLayer) -> Self {
self.port_link_layer = Some(link_layer);
self
}
#[inline]
pub fn numa_node(mut self, node: u8) -> Self {
self.numa_nodes.push(node);
self
}
#[inline]
pub fn probe(self) -> Result<Nic, NicProbeError> {
self.probe_nth_dev(0)
}
pub fn probe_nth_dev(self, mut n: usize) -> Result<Nic, NicProbeError> {
let dev_list = IbvDeviceList::new()?;
for dev in &dev_list {
let ctx = dev.open()?;
if self.is_device_eligible(ctx) {
let attr = ctx.query_device()?;
if (1..=attr.phys_port_cnt).any(|port_num| self.is_port_eligible(ctx, port_num)) {
if n == 0 {
let ports = (1..=attr.phys_port_cnt)
.map(|port_num| Port::new(ctx, port_num))
.collect::<Result<Vec<_>, _>>()?;
return Ok(Nic {
context: Context::new(ctx, attr),
ports,
});
} else {
n -= 1;
}
}
}
unsafe { ctx.close()? };
}
Err(NicProbeError::NotFound)
}
pub fn probe_nth_port(self, mut n: usize) -> Result<Nic, NicProbeError> {
let dev_list = IbvDeviceList::new()?;
for dev in &dev_list {
let ctx = dev.open()?;
if self.is_device_eligible(ctx) {
let attr = ctx.query_device()?;
for port_num in 1..=attr.phys_port_cnt {
if self.is_port_eligible(ctx, port_num) {
if n == 0 {
let port = Port::new(ctx, port_num)?;
return Ok(Nic {
context: Context::new(ctx, attr),
ports: vec![port],
});
} else {
n -= 1;
}
}
}
}
unsafe { ctx.close()? };
}
Err(NicProbeError::NotFound)
}
}
impl Default for NicFinder {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Error)]
pub enum NicProbeError {
#[error("I/O error from ibverbs")]
IoError(#[from] IoError),
#[error("port query error")]
PortQueryError(#[from] PortQueryError),
#[error("no eligible RDMA device found")]
NotFound,
}
pub struct Nic {
pub context: Context,
pub ports: Vec<Port>,
}
impl Nic {
#[inline]
pub fn finder() -> NicFinder {
Default::default()
}
}