use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};
use arcbox_virtio::{GuestMemWriter, QueueConfig, SplitQueue};
use crate::device::VirtioMmioState;
use crate::irq::Irq;
const BATCH_SIZE: usize = 64;
const COALESCE_COUNT: u16 = 64;
const COALESCE_TIMEOUT: Duration = Duration::from_micros(50);
const POLL_TIMEOUT: Duration = Duration::from_millis(1);
pub struct NetRxWorkerContext {
pub net_host_fd: i32,
pub guest_mem: Arc<GuestMemWriter>,
pub rx_queue: QueueConfig,
pub event_idx: bool,
pub mmio_state: Arc<RwLock<VirtioMmioState>>,
pub irq_callback: Arc<dyn Fn(Irq, bool) -> crate::error::Result<()> + Send + Sync>,
pub irq: Irq,
pub exit_vcpus: Arc<dyn Fn() + Send + Sync>,
pub running: Arc<AtomicBool>,
}
fn trigger_net_irq(ctx: &NetRxWorkerContext) {
if let Ok(mut s) = ctx.mmio_state.write() {
s.trigger_interrupt(1); }
let _ = (ctx.irq_callback)(ctx.irq, true);
(ctx.exit_vcpus)();
}
fn flush_batch(ctx: &NetRxWorkerContext, queue: &SplitQueue, fire: bool) {
if ctx.event_idx {
queue.write_avail_event_current();
}
if fire {
trigger_net_irq(ctx);
}
}
pub fn net_rx_worker_loop(ctx: NetRxWorkerContext) {
tracing::info!(
"net-io worker started (fd={}, queue_size={})",
ctx.net_host_fd,
ctx.rx_queue.size
);
let kq = unsafe { libc::kqueue() };
if kq < 0 {
tracing::error!(
"net-io: kqueue creation failed: {}",
std::io::Error::last_os_error()
);
return;
}
let changelist = libc::kevent {
ident: ctx.net_host_fd as usize,
filter: libc::EVFILT_READ,
flags: libc::EV_ADD | libc::EV_ENABLE,
fflags: 0,
data: 0,
udata: std::ptr::null_mut(),
};
let ret = unsafe {
libc::kevent(
kq,
&raw const changelist,
1,
std::ptr::null_mut(),
0,
std::ptr::null(),
)
};
if ret < 0 {
tracing::error!(
"net-io: kevent registration failed: {}",
std::io::Error::last_os_error()
);
unsafe { libc::close(kq) };
return;
}
let timeout = libc::timespec {
tv_sec: 0,
tv_nsec: POLL_TIMEOUT.as_nanos() as i64,
};
let mut queue = SplitQueue::new(Arc::clone(&ctx.guest_mem), 0, &ctx.rx_queue, ctx.event_idx);
queue.set_last_avail_idx(ctx.guest_mem.read_u16(ctx.rx_queue.used_addr as usize + 2));
let mut pending_frames: u16 = 0;
let mut batch_start: Option<Instant> = None;
let mut fire = false;
loop {
if !ctx.running.load(Ordering::Relaxed) {
break;
}
let mut event = libc::kevent {
ident: 0,
filter: 0,
flags: 0,
fflags: 0,
data: 0,
udata: std::ptr::null_mut(),
};
let nev = unsafe {
libc::kevent(
kq,
std::ptr::null(),
0,
&raw mut event,
1,
&raw const timeout,
)
};
if nev > 0 {
let mut frame_buf = [0u8; 2048];
for _ in 0..BATCH_SIZE {
let n = unsafe {
libc::read(
ctx.net_host_fd,
frame_buf.as_mut_ptr().cast::<libc::c_void>(),
frame_buf.len(),
)
};
if n <= 0 {
break; }
let frame = &frame_buf[..n as usize];
if let Some(notify) = arcbox_net_inject::queue::inject_one_frame(&mut queue, frame)
{
fire |= notify;
pending_frames += 1;
if batch_start.is_none() {
batch_start = Some(Instant::now());
}
} else {
if pending_frames > 0 {
flush_batch(&ctx, &queue, fire);
pending_frames = 0;
batch_start = None;
fire = false;
}
std::thread::sleep(Duration::from_micros(100));
break;
}
if pending_frames >= COALESCE_COUNT {
flush_batch(&ctx, &queue, fire);
pending_frames = 0;
batch_start = None;
fire = false;
}
}
}
if pending_frames > 0 {
if let Some(start) = batch_start {
if start.elapsed() >= COALESCE_TIMEOUT {
flush_batch(&ctx, &queue, fire);
pending_frames = 0;
batch_start = None;
fire = false;
}
}
}
}
if pending_frames > 0 {
if ctx.event_idx {
queue.write_avail_event_current();
}
trigger_net_irq(&ctx); }
unsafe { libc::close(kq) };
tracing::info!("net-io worker stopped");
}