Skip to content

Commit ccda0b0

Browse files
committed
feat: add VIRTIO_NET_F_MRG_RXBUF path to virtio-net
Add new path for virtio-net if VIRTIO_NET_F_MRG_RXBUF feature is negotiated. Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent 5d57e13 commit ccda0b0

File tree

1 file changed

+177
-4
lines changed

1 file changed

+177
-4
lines changed

src/vmm/src/devices/virtio/net/device.rs

Lines changed: 177 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@
99
use std::io::Read;
1010
use std::mem;
1111
use std::net::Ipv4Addr;
12+
use std::num::Wrapping;
1213
use std::sync::atomic::AtomicU32;
1314
use std::sync::{Arc, Mutex};
1415

15-
use libc::EAGAIN;
16+
use libc::{iovec, EAGAIN};
1617
use log::{error, warn};
1718
use utils::eventfd::EventFd;
1819
use utils::net::mac::MacAddr;
1920
use utils::u64_to_usize;
20-
use vm_memory::GuestMemoryError;
21+
use vm_memory::{GuestMemory, GuestMemoryError};
2122

2223
use crate::devices::virtio::device::{DeviceState, IrqTrigger, IrqType, VirtioDevice};
2324
use crate::devices::virtio::gen::virtio_blk::VIRTIO_F_VERSION_1;
@@ -33,7 +34,7 @@ use crate::devices::virtio::net::tap::Tap;
3334
use crate::devices::virtio::net::{
3435
gen, NetError, NetQueue, MAX_BUFFER_SIZE, NET_QUEUE_SIZES, RX_INDEX, TX_INDEX,
3536
};
36-
use crate::devices::virtio::queue::{DescriptorChain, Queue};
37+
use crate::devices::virtio::queue::{Descriptor, DescriptorChain, Queue};
3738
use crate::devices::virtio::{ActivateError, TYPE_NET};
3839
use crate::devices::{report_net_event_fail, DeviceError};
3940
use crate::dumbo::pdu::arp::ETH_IPV4_FRAME_LEN;
@@ -128,6 +129,8 @@ pub struct Net {
128129

129130
rx_bytes_read: usize,
130131
rx_frame_buf: [u8; MAX_BUFFER_SIZE],
132+
rx_iov: Vec<iovec>,
133+
rx_heads_info: Vec<(u16, u32, usize)>,
131134

132135
tx_frame_headers: [u8; frame_hdr_len()],
133136

@@ -145,6 +148,12 @@ pub struct Net {
145148
pub(crate) metrics: Arc<NetDeviceMetrics>,
146149
}
147150

151+
// This is needed for rx_iov as it uses iovec struct
152+
// which contains *mut c_void type.
153+
// SAFETY:
154+
// Safe as only vmm thread will access rx_iov field.
155+
unsafe impl Send for Net {}
156+
148157
impl Net {
149158
/// Create a new virtio network device with the given TAP interface.
150159
pub fn new_with_tap(
@@ -161,6 +170,7 @@ impl Net {
161170
| 1 << VIRTIO_NET_F_HOST_TSO4
162171
| 1 << VIRTIO_NET_F_HOST_UFO
163172
| 1 << VIRTIO_F_VERSION_1
173+
| 1 << VIRTIO_NET_F_MRG_RXBUF
164174
| 1 << VIRTIO_RING_F_EVENT_IDX;
165175

166176
let mut config_space = ConfigSpace::default();
@@ -190,6 +200,8 @@ impl Net {
190200
rx_deferred_frame: false,
191201
rx_bytes_read: 0,
192202
rx_frame_buf: [0u8; MAX_BUFFER_SIZE],
203+
rx_iov: Vec::new(),
204+
rx_heads_info: Vec::new(),
193205
tx_frame_headers: [0u8; frame_hdr_len()],
194206
irq_trigger: IrqTrigger::new().map_err(NetError::EventFd)?,
195207
config_space,
@@ -528,7 +540,7 @@ impl Net {
528540
if !self.has_feature(u64::from(VIRTIO_NET_F_MRG_RXBUF)) {
529541
self.process_rx_orig()
530542
} else {
531-
unimplemented!();
543+
self.process_rx_iov()
532544
}
533545
}
534546

@@ -568,6 +580,167 @@ impl Net {
568580
self.signal_used_queue(NetQueue::Rx)
569581
}
570582

583+
fn process_rx_iov(&mut self) -> Result<(), DeviceError> {
584+
self.rx_iov.clear();
585+
self.rx_heads_info.clear();
586+
587+
let mem = self.device_state.mem().unwrap();
588+
589+
// Preparing iov vector with all available buffers
590+
// from all available descriptor chains
591+
#[repr(C)]
592+
struct AvailRing {
593+
flags: u16,
594+
idx: u16,
595+
ring: [u16; 256],
596+
used_element: u16,
597+
}
598+
// SAFETY:
599+
// avail_ring in the queue is a valid guest address
600+
let avail_ring: &AvailRing = unsafe {
601+
std::mem::transmute(
602+
mem.get_host_address(self.queues[RX_INDEX].avail_ring)
603+
.unwrap(),
604+
)
605+
};
606+
// SAFETY:
607+
// desc_table in the queue is a valid guest address
608+
let desc_table: &[Descriptor; 256] = unsafe {
609+
std::mem::transmute(
610+
mem.get_host_address(self.queues[RX_INDEX].desc_table)
611+
.unwrap(),
612+
)
613+
};
614+
615+
let avail_idx = self.queues[RX_INDEX].avail_idx(mem);
616+
let actual_size = self.queues[RX_INDEX].actual_size();
617+
let mut next_avail = self.queues[RX_INDEX].next_avail;
618+
619+
while next_avail.0 != avail_idx.0 {
620+
let index = next_avail.0 % actual_size;
621+
let desc_index = avail_ring.ring[index as usize];
622+
623+
let mut desc = &desc_table[desc_index as usize];
624+
625+
let mut chain_capacity = desc.len;
626+
let mut iov_len = 1;
627+
self.rx_iov.push(iovec {
628+
iov_base: mem
629+
.get_host_address(vm_memory::GuestAddress(desc.addr))
630+
.unwrap()
631+
.cast(),
632+
iov_len: desc.len as usize,
633+
});
634+
635+
while desc.flags & crate::devices::virtio::queue::VIRTQ_DESC_F_NEXT != 0 {
636+
desc = &desc_table[desc.next as usize];
637+
chain_capacity += desc.len;
638+
iov_len += 1;
639+
self.rx_iov.push(iovec {
640+
iov_base: mem
641+
.get_host_address(vm_memory::GuestAddress(desc.addr))
642+
.unwrap()
643+
.cast(),
644+
iov_len: desc.len as usize,
645+
});
646+
}
647+
648+
self.rx_heads_info
649+
.push((desc_index, chain_capacity, iov_len));
650+
651+
next_avail += Wrapping(1);
652+
}
653+
654+
// If threre are no buffers, there is
655+
// nothing for us to do.
656+
if self.rx_iov.is_empty() {
657+
return Ok(());
658+
}
659+
660+
let mut iov_slice = &self.rx_iov[..];
661+
let mut heads_info_slice = &self.rx_heads_info[..];
662+
loop {
663+
// If we used all iovs, we cannot read
664+
// anything else.
665+
if iov_slice.is_empty() {
666+
break;
667+
}
668+
669+
match self.tap.read_iovec(iov_slice).map_err(NetError::IO) {
670+
Ok(mut bytes_written) => {
671+
let mut used_iovs = 0;
672+
let mut used_heads = 0;
673+
// Calculate how manu descriptor heads we used for this write.
674+
loop {
675+
if used_heads == heads_info_slice.len() {
676+
break;
677+
}
678+
679+
let (head_index, iov_capacity, iov_len) = heads_info_slice[used_heads];
680+
used_heads += 1;
681+
used_iovs += iov_len;
682+
683+
if bytes_written < iov_capacity as usize {
684+
self.queues[RX_INDEX]
685+
.add_used(mem, head_index, bytes_written as u32)
686+
.unwrap();
687+
break;
688+
} else {
689+
self.queues[RX_INDEX]
690+
.add_used(mem, head_index, iov_capacity)
691+
.unwrap();
692+
bytes_written -= iov_capacity as usize;
693+
};
694+
}
695+
696+
// Update number of descrptor heads used to store
697+
// a packet.
698+
// SAFETY:
699+
// The iov_base is valid userspace address.
700+
#[allow(clippy::transmute_ptr_to_ref)]
701+
let header: &mut virtio_net_hdr_v1 =
702+
unsafe { std::mem::transmute(iov_slice[0].iov_base) };
703+
header.num_buffers = used_heads as u16;
704+
705+
iov_slice = &iov_slice[used_iovs..];
706+
heads_info_slice = &heads_info_slice[used_heads..];
707+
708+
// Update the number of used descriptor heads
709+
self.queues[RX_INDEX].next_avail += Wrapping(used_heads as u16);
710+
711+
// Tell the guest about used descriptor heads.
712+
if self.queues[RX_INDEX].uses_notif_suppression {
713+
if self.queues[RX_INDEX].len(mem) == 0 {
714+
let next_avail = self.queues[RX_INDEX].next_avail.0;
715+
self.queues[RX_INDEX].set_avail_event(next_avail, mem);
716+
}
717+
} else {
718+
let next_avail = self.queues[RX_INDEX].next_avail.0;
719+
self.queues[RX_INDEX].set_avail_event(next_avail, mem);
720+
}
721+
}
722+
Err(NetError::IO(err)) => {
723+
// The tap device is non-blocking, so any error aside from EAGAIN is
724+
// unexpected.
725+
match err.raw_os_error() {
726+
Some(err) if err == EAGAIN => (),
727+
_ => {
728+
error!("Failed to read tap: {:?}", err);
729+
self.metrics.tap_read_fails.inc();
730+
return Err(DeviceError::FailedReadTap);
731+
}
732+
};
733+
break;
734+
}
735+
Err(err) => {
736+
error!("Spurious error in network RX: {:?}", err);
737+
}
738+
}
739+
}
740+
self.signal_used_queue(NetQueue::Rx)?;
741+
Ok(())
742+
}
743+
571744
// Process the deferred frame first, then continue reading from tap.
572745
fn handle_deferred_frame(&mut self) -> Result<(), DeviceError> {
573746
if self.rate_limited_rx_single_frame() {

0 commit comments

Comments
 (0)