use crate::ffi;
use crate::mempool::Mempool;
const RX_DESC: u16 = 1024;
const TX_DESC: u16 = 1024;
#[derive(Debug, Clone, Copy, Default)]
pub struct ChecksumOffloads {
pub rx_ip: bool,
pub rx_tcp: bool,
pub tx_ip: bool,
pub tx_tcp: bool,
}
impl ChecksumOffloads {
pub fn intersect(self, other: Self) -> Self {
ChecksumOffloads {
rx_ip: self.rx_ip && other.rx_ip,
rx_tcp: self.rx_tcp && other.rx_tcp,
tx_ip: self.tx_ip && other.tx_ip,
tx_tcp: self.tx_tcp && other.tx_tcp,
}
}
}
pub struct Port {
port_id: u16,
pub num_queues: u16,
started: bool,
pub offloads: ChecksumOffloads,
}
impl Port {
pub fn configure(port_id: u16, mempool: &Mempool) -> Result<Self, PortError> {
Self::configure_with_vlan(port_id, mempool, None, 1)
}
pub fn configure_with_vlan(
port_id: u16,
mempool: &Mempool,
vlan_id: Option<u16>,
num_queues: u16,
) -> Result<Self, PortError> {
let mut dev_info: ffi::rte_eth_dev_info = unsafe { std::mem::zeroed() };
let ret = unsafe { ffi::rte_eth_dev_info_get(port_id, &mut dev_info) };
if ret != 0 {
return Err(PortError::InfoFailed(ret));
}
let max_queues = dev_info.max_rx_queues.min(dev_info.max_tx_queues).max(1);
let requested_queues = num_queues;
let num_queues = num_queues.min(max_queues);
if num_queues < requested_queues {
tracing::warn!(
port_id,
requested = requested_queues,
actual = num_queues,
"clamped queue count to NIC maximum"
);
}
let rx_cksum_wanted = unsafe { ffi::dpdk_rx_offload_checksum() };
let tx_cksum_wanted = unsafe { ffi::dpdk_tx_offload_checksum() };
let mut rx_offloads = dev_info.rx_offload_capa & rx_cksum_wanted;
let mut tx_offloads = dev_info.tx_offload_capa & tx_cksum_wanted;
let vlan_strip = unsafe { ffi::dpdk_rx_offload_vlan_strip() };
let vlan_insert = unsafe { ffi::dpdk_tx_offload_vlan_insert() };
if vlan_id.is_some() {
if dev_info.rx_offload_capa & vlan_strip != 0 {
rx_offloads |= vlan_strip;
tracing::info!(port_id, "VLAN strip enabled (RX)");
} else {
tracing::warn!(port_id, "NIC does not support RX VLAN strip");
}
if dev_info.tx_offload_capa & vlan_insert != 0 {
tx_offloads |= vlan_insert;
tracing::info!(port_id, "VLAN insert enabled (TX)");
} else {
tracing::warn!(port_id, "NIC does not support TX VLAN insert");
}
}
let mut port_conf: ffi::rte_eth_conf = unsafe { std::mem::zeroed() };
port_conf.rxmode.offloads = rx_offloads;
port_conf.txmode.offloads = tx_offloads;
if num_queues > 1 {
let rss_hf = unsafe { ffi::dpdk_eth_rss_ip() | ffi::dpdk_eth_rss_tcp() };
let supported_hf = dev_info.flow_type_rss_offloads;
port_conf.rx_adv_conf.rss_conf.rss_hf = rss_hf & supported_hf;
port_conf.rx_adv_conf.rss_conf.rss_key = std::ptr::null_mut(); port_conf.rx_adv_conf.rss_conf.rss_key_len = 0;
tracing::info!(
port_id,
num_queues,
rss_hf = rss_hf & supported_hf,
"RSS enabled"
);
}
let offloads = ChecksumOffloads {
rx_ip: rx_offloads & (1u64 << 1) != 0, rx_tcp: rx_offloads & (1u64 << 3) != 0, tx_ip: tx_offloads & (1u64 << 1) != 0, tx_tcp: tx_offloads & (1u64 << 3) != 0, };
let ret =
unsafe { ffi::rte_eth_dev_configure(port_id, num_queues, num_queues, &port_conf) };
if ret != 0 {
return Err(PortError::ConfigureFailed(ret));
}
let socket_id = unsafe { ffi::rte_eth_dev_socket_id(port_id) };
for q in 0..num_queues {
let ret = unsafe {
ffi::rte_eth_rx_queue_setup(
port_id,
q,
RX_DESC,
socket_id as libc::c_uint,
std::ptr::null(), mempool.as_raw(),
)
};
if ret != 0 {
return Err(PortError::RxQueueFailed(ret));
}
}
for q in 0..num_queues {
let ret = unsafe {
ffi::rte_eth_tx_queue_setup(
port_id,
q,
TX_DESC,
socket_id as libc::c_uint,
std::ptr::null(), )
};
if ret != 0 {
return Err(PortError::TxQueueFailed(ret));
}
}
let ret = unsafe { ffi::rte_eth_promiscuous_enable(port_id) };
if ret != 0 {
tracing::warn!(port_id, ret, "failed to enable promiscuous mode");
}
tracing::info!(
port_id,
num_queues,
rx_desc = RX_DESC,
tx_desc = TX_DESC,
?offloads,
"DPDK port configured"
);
Ok(Port {
port_id,
num_queues,
started: false,
offloads,
})
}
pub fn start(&mut self) -> Result<(), PortError> {
let ret = unsafe { ffi::rte_eth_dev_start(self.port_id) };
if ret != 0 {
return Err(PortError::StartFailed(ret));
}
self.started = true;
tracing::info!(port_id = self.port_id, "DPDK port started");
Ok(())
}
pub fn mac_addr(&self) -> [u8; 6] {
let mut addr: ffi::rte_ether_addr = unsafe { std::mem::zeroed() };
unsafe {
ffi::rte_eth_macaddr_get(self.port_id, &mut addr);
}
addr.addr_bytes
}
pub fn port_id(&self) -> u16 {
self.port_id
}
}
impl Drop for Port {
fn drop(&mut self) {
if self.started {
unsafe {
ffi::rte_eth_dev_stop(self.port_id);
}
tracing::info!(port_id = self.port_id, "DPDK port stopped");
}
}
}
#[derive(Debug)]
pub enum PortError {
InfoFailed(i32),
ConfigureFailed(i32),
RxQueueFailed(i32),
TxQueueFailed(i32),
StartFailed(i32),
}
impl std::fmt::Display for PortError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PortError::InfoFailed(c) => write!(f, "rte_eth_dev_info_get failed: {c}"),
PortError::ConfigureFailed(c) => write!(f, "rte_eth_dev_configure failed: {c}"),
PortError::RxQueueFailed(c) => write!(f, "rte_eth_rx_queue_setup failed: {c}"),
PortError::TxQueueFailed(c) => write!(f, "rte_eth_tx_queue_setup failed: {c}"),
PortError::StartFailed(c) => write!(f, "rte_eth_dev_start failed: {c}"),
}
}
}
impl std::error::Error for PortError {}