Skip to content

Commit 462fffb

Browse files
committed
feat(rvnic): add poll support for RX ring notification
1 parent ad8b4de commit 462fffb

File tree

3 files changed

+130
-42
lines changed

3 files changed

+130
-42
lines changed

rvnic/kernel/rattan_vnic.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <linux/atomic.h>
3030
#include <linux/ktime.h>
3131
#include <linux/workqueue.h>
32+
#include <linux/poll.h>
3233

3334
#include "rattan_vnic.h"
3435

@@ -205,6 +206,10 @@ static netdev_tx_t rattan_vnic_start_xmit(struct sk_buff *skb,
205206

206207
/* Success */
207208
rcu_read_unlock();
209+
210+
/* Wake up poll waiters */
211+
wake_up_interruptible(&ctx->wait);
212+
208213
dev->stats.tx_packets++;
209214
dev->stats.tx_bytes += skb->len;
210215
return NETDEV_TX_OK;
@@ -1198,6 +1203,7 @@ static int rattan_vnic_fop_open(struct inode *inode, struct file *file)
11981203
ctx->started = false;
11991204
RCU_INIT_POINTER(ctx->umem, NULL);
12001205
RCU_INIT_POINTER(ctx->rings, NULL);
1206+
init_waitqueue_head(&ctx->wait);
12011207

12021208
/* Register network device */
12031209
err = register_netdev(netdev);
@@ -1270,11 +1276,49 @@ static int rattan_vnic_fop_release(struct inode *inode, struct file *file)
12701276
return 0;
12711277
}
12721278

1279+
/*
1280+
* Poll implementation - allows userspace to block until RX data is available
1281+
*
1282+
* Returns:
1283+
* EPOLLIN | EPOLLRDNORM - RX ring has data (producer != consumer)
1284+
* EPOLLERR - device not configured
1285+
*/
1286+
static __poll_t rattan_vnic_poll(struct file *file, poll_table *wait)
1287+
{
1288+
struct rattan_vnic_ctx *ctx = file->private_data;
1289+
struct rattan_rings *rings;
1290+
__poll_t mask = 0;
1291+
u32 rx_prod, rx_cons;
1292+
1293+
if (!ctx)
1294+
return EPOLLERR;
1295+
1296+
poll_wait(file, &ctx->wait, wait);
1297+
1298+
rcu_read_lock();
1299+
rings = rcu_dereference(ctx->rings);
1300+
if (!rings || !rings->rx) {
1301+
rcu_read_unlock();
1302+
return EPOLLERR;
1303+
}
1304+
1305+
/* Check RX ring: kernel produces, userspace consumes */
1306+
rx_prod = smp_load_acquire(&rings->rx->ring.producer);
1307+
rx_cons = READ_ONCE(rings->rx->ring.consumer);
1308+
if (rx_prod != rx_cons)
1309+
mask |= EPOLLIN | EPOLLRDNORM;
1310+
1311+
rcu_read_unlock();
1312+
1313+
return mask;
1314+
}
1315+
12731316
static const struct file_operations rattan_vnic_fops = {
12741317
.owner = THIS_MODULE,
12751318
.open = rattan_vnic_fop_open,
12761319
.release = rattan_vnic_fop_release,
12771320
.unlocked_ioctl = rattan_vnic_ioctl,
1321+
.poll = rattan_vnic_poll,
12781322
};
12791323

12801324
static struct miscdevice rattan_vnic_misc = {

rvnic/kernel/rattan_vnic.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <linux/refcount.h>
1717
#include <linux/ktime.h>
1818
#include <linux/spinlock.h>
19+
#include <linux/wait.h>
1920

2021
#include "rattan_vnic_uapi.h"
2122

@@ -114,6 +115,9 @@ struct rattan_vnic_ctx {
114115

115116
struct rattan_umem __rcu *umem;
116117
struct rattan_rings __rcu *rings;
118+
119+
/* Poll wait queue - for blocking waits on RX availability */
120+
wait_queue_head_t wait;
117121
};
118122

119123
#endif /* _RATTAN_VNIC_INTERNAL_H */

rvnic/librvnic/examples/test_forward.rs

Lines changed: 82 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
1818
use rvnic::sys::RattanDesc;
1919
use rvnic::{CompRing, FillRing, RATTAN_RING_SIZE, Rings, RvnicDevice, RxRing, TxRing, UmemBuilder};
20+
use std::os::unix::io::AsRawFd;
2021
use std::process::{Child, Command, Stdio};
2122
use std::sync::Arc;
2223
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
@@ -284,65 +285,105 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
284285
let packets_forwarded = Arc::new(AtomicU64::new(0));
285286
let packets_forwarded_clone = packets_forwarded.clone();
286287

287-
// Two forwarding threads - one per direction
288-
let running_clone2 = running.clone();
289-
let packets_forwarded_clone2 = packets_forwarded.clone();
288+
// Get file descriptors for epoll
289+
let dev0_fd = dev0.as_raw_fd();
290+
let dev1_fd = dev1.as_raw_fd();
290291

291-
// Thread 1: rx0 → tx1, recycle comp1 → fill0
292-
let thread1 = std::thread::spawn(move || {
292+
// Single-threaded forwarder using epoll
293+
let forward_thread = std::thread::spawn(move || {
293294
let mut descs = [RattanDesc::default(); BATCH_SIZE];
294295
let mut addrs = [0u64; BATCH_SIZE];
295296

297+
// Create epoll instance
298+
let epfd = unsafe { libc::epoll_create1(0) };
299+
if epfd < 0 {
300+
eprintln!("epoll_create1 failed");
301+
return (dev0, dev1);
302+
}
303+
304+
// Add dev0 to epoll (data.u32 = 0)
305+
let mut ev = libc::epoll_event {
306+
events: libc::EPOLLIN as u32,
307+
u64: 0,
308+
};
309+
if unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, dev0_fd, &mut ev) } < 0 {
310+
eprintln!("epoll_ctl add dev0 failed");
311+
unsafe { libc::close(epfd) };
312+
return (dev0, dev1);
313+
}
314+
315+
// Add dev1 to epoll (data.u32 = 1)
316+
ev.u64 = 1;
317+
if unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, dev1_fd, &mut ev) } < 0 {
318+
eprintln!("epoll_ctl add dev1 failed");
319+
unsafe { libc::close(epfd) };
320+
return (dev0, dev1);
321+
}
322+
323+
let mut events = [libc::epoll_event { events: 0, u64: 0 }; 2];
324+
296325
while running_clone.load(Ordering::Relaxed) {
297-
// Forward a batch
298-
let fwd = forward_batch(&mut rx0, &mut tx1, &mut descs);
326+
// Wait for events on either device (10ms timeout)
327+
let nfds = unsafe { libc::epoll_wait(epfd, events.as_mut_ptr(), 2, 10) };
299328

300-
// Aggressively recycle all available chunks
301-
loop {
302-
let recycled = recycle_chunks(&mut comp1, &mut fill0, &mut addrs);
303-
if recycled == 0 {
329+
if nfds < 0 {
330+
let err = std::io::Error::last_os_error();
331+
if err.kind() != std::io::ErrorKind::Interrupted {
332+
eprintln!("epoll_wait failed: {}", err);
304333
break;
305334
}
335+
continue;
306336
}
307337

308-
if fwd > 0 {
309-
packets_forwarded_clone.fetch_add(fwd as u64, Ordering::Relaxed);
310-
let _ = dev1.kick_rx();
311-
} else {
312-
std::hint::spin_loop();
338+
// Process events - drain RX rings completely
339+
for i in 0..nfds as usize {
340+
let dev_idx = events[i].u64;
341+
342+
if dev_idx == 0 {
343+
// dev0 has RX data: forward rx0 → tx1
344+
// Drain all available packets (not just one batch)
345+
loop {
346+
let fwd = forward_batch(&mut rx0, &mut tx1, &mut descs);
347+
if fwd > 0 {
348+
packets_forwarded_clone.fetch_add(fwd as u64, Ordering::Relaxed);
349+
let _ = dev1.kick_rx();
350+
}
351+
if fwd < BATCH_SIZE {
352+
break; // Ring drained
353+
}
354+
}
355+
} else {
356+
// dev1 has RX data: forward rx1 → tx0
357+
// Drain all available packets (not just one batch)
358+
loop {
359+
let fwd = forward_batch(&mut rx1, &mut tx0, &mut descs);
360+
if fwd > 0 {
361+
packets_forwarded_clone.fetch_add(fwd as u64, Ordering::Relaxed);
362+
let _ = dev0.kick_rx();
363+
}
364+
if fwd < BATCH_SIZE {
365+
break; // Ring drained
366+
}
367+
}
368+
}
313369
}
314-
}
315-
dev1
316-
});
317-
318-
// Thread 2: rx1 → tx0, recycle comp0 → fill1
319-
let thread2 = std::thread::spawn(move || {
320-
let mut descs = [RattanDesc::default(); BATCH_SIZE];
321-
let mut addrs = [0u64; BATCH_SIZE];
322370

323-
while running_clone2.load(Ordering::Relaxed) {
324-
// Forward a batch
325-
let fwd = forward_batch(&mut rx1, &mut tx0, &mut descs);
326-
327-
// Aggressively recycle all available chunks
371+
// Always recycle chunks from both directions
372+
// This ensures chunks are returned promptly regardless of which device triggered
328373
loop {
329-
let recycled = recycle_chunks(&mut comp0, &mut fill1, &mut addrs);
330-
if recycled == 0 {
374+
let r0 = recycle_chunks(&mut comp1, &mut fill0, &mut addrs);
375+
let r1 = recycle_chunks(&mut comp0, &mut fill1, &mut addrs);
376+
if r0 == 0 && r1 == 0 {
331377
break;
332378
}
333379
}
334-
335-
if fwd > 0 {
336-
packets_forwarded_clone2.fetch_add(fwd as u64, Ordering::Relaxed);
337-
let _ = dev0.kick_rx();
338-
} else {
339-
std::hint::spin_loop();
340-
}
341380
}
342-
dev0
381+
382+
unsafe { libc::close(epfd) };
383+
(dev0, dev1)
343384
});
344385

345-
println!(" Forwarding loop started");
386+
println!(" Forwarding loop started (single thread, epoll)");
346387

347388
// Step 5: Run iperf test
348389
println!("\n5. Running iperf3 performance test...");
@@ -377,8 +418,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
377418
println!("\n7. Cleaning up...");
378419
running.store(false, Ordering::Relaxed);
379420

380-
let _dev1 = thread1.join().unwrap();
381-
let _dev0 = thread2.join().unwrap();
421+
let (_dev0, _dev1) = forward_thread.join().unwrap();
382422

383423
// netns_guard will clean up namespaces on drop
384424
drop(netns_guard);

0 commit comments

Comments
 (0)