Skip to content

Commit 274bb46

Browse files
committed
feat(net): add RxBuffer type
This type will be used in the next commit where we will switch to use `readv` for RX path. Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent 1f95ca8 commit 274bb46

File tree

3 files changed

+410
-0
lines changed

3 files changed

+410
-0
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,17 @@ pub mod device;
2121
mod event_handler;
2222
pub mod metrics;
2323
pub mod persist;
24+
pub mod rx_buffer;
2425
mod tap;
2526
pub mod test_utils;
2627

2728
mod gen;
2829

2930
pub use tap::{Tap, TapError};
31+
use vm_memory::VolatileMemoryError;
3032

3133
pub use self::device::Net;
34+
use self::rx_buffer::RxBufferError;
3235

3336
/// Enum representing the Net device queue types
3437
#[derive(Debug)]
@@ -50,6 +53,10 @@ pub enum NetError {
5053
EventFd(io::Error),
5154
/// IO error: {0}
5255
IO(io::Error),
56+
/// Error writing in guest memory: {0}
57+
GuestMemoryError(#[from] VolatileMemoryError),
5358
/// The VNET header is missing from the frame
5459
VnetHeaderMissing,
60+
/// RxBuffer error: {0}
61+
RxBufferError(#[from] RxBufferError),
5562
}
Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use std::num::Wrapping;
5+
6+
use libc::{c_void, iovec, size_t};
7+
use serde::{Deserialize, Serialize};
8+
use vm_memory::bitmap::Bitmap;
9+
use vm_memory::{GuestMemory, GuestMemoryError};
10+
11+
use crate::devices::virtio::gen::virtio_net::virtio_net_hdr_v1;
12+
use crate::devices::virtio::iov_ring_buffer::{IovRingBuffer, IovRingBufferError};
13+
use crate::devices::virtio::queue::{DescriptorChain, Queue, FIRECRACKER_MAX_QUEUE_SIZE};
14+
use crate::logger::error;
15+
use crate::utils::ring_buffer::RingBuffer;
16+
use crate::vstate::memory::GuestMemoryMmap;
17+
18+
#[derive(Debug, Clone, Serialize, Deserialize)]
19+
pub struct RxBufferState {
20+
pub chains_count: u16,
21+
pub used_descriptors: u16,
22+
}
23+
24+
impl RxBufferState {
25+
pub fn from_rx_buffer(buffer: &RxBuffer) -> Self {
26+
// The maximum number of chains is the maximum size of the queue
27+
// which is FIRECRACKER_MAX_QUEUE_SIZE (256).
28+
Self {
29+
chains_count: buffer.chain_infos.len().try_into().unwrap(),
30+
used_descriptors: buffer.used_descriptors,
31+
}
32+
}
33+
}
34+
35+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
36+
pub struct ChainInfo {
37+
pub head_index: u16,
38+
pub chain_len: u16,
39+
}
40+
41+
#[derive(Debug, thiserror::Error, displaydoc::Display)]
42+
pub enum RxBufferError {
43+
/// Cannot create `RxBuffer` due to error for `IovRingBuffer`: {0}
44+
New(#[from] IovRingBufferError),
45+
/// Guest memory error: {0}
46+
GuestMemory(#[from] GuestMemoryError),
47+
/// Tried to add a read-only descriptor chain to the `RxBuffer`
48+
ReadOnlyDescriptor,
49+
/// Tried to write more bytes than `RxBuffer` can hold.
50+
WriteOverflow,
51+
}
52+
53+
/// A map of all the memory the guest has provided us with for performing RX
54+
#[derive(Debug)]
55+
pub struct RxBuffer {
56+
// An ring covering all the memory we have available for receiving network
57+
// frames.
58+
pub iovecs: IovRingBuffer,
59+
// Ring buffer of meta data about descriptor chains stored in the `iov_ring`.
60+
pub chain_infos: RingBuffer<ChainInfo>,
61+
// Number of descriptor chains we have used to process packets.
62+
pub used_descriptors: u16,
63+
}
64+
65+
impl RxBuffer {
66+
/// Create a new [`RxBuffers`] object for storing guest memory for performing RX
67+
pub fn new() -> Result<Self, RxBufferError> {
68+
Ok(Self {
69+
iovecs: IovRingBuffer::new()?,
70+
chain_infos: RingBuffer::new_with_size(u32::from(FIRECRACKER_MAX_QUEUE_SIZE)),
71+
used_descriptors: 0,
72+
})
73+
}
74+
75+
/// Is number of iovecs is zero.
76+
pub fn is_empty(&self) -> bool {
77+
self.iovecs.is_empty()
78+
}
79+
80+
/// Returns a slice of underlying iovec for the first chain
81+
/// in the buffer.
82+
pub fn one_chain_mut_slice(&mut self) -> &mut [iovec] {
83+
if let Some(chain_info) = self.chain_infos.first() {
84+
let chain_len = usize::from(chain_info.chain_len);
85+
&mut self.iovecs.as_mut_slice()[0..chain_len]
86+
} else {
87+
&mut []
88+
}
89+
}
90+
91+
/// Add a new `DescriptorChain` that we received from the RX queue in the buffer.
92+
///
93+
/// # Safety
94+
/// The `DescriptorChain` cannot be referencing the same memory location as any other
95+
/// `DescriptorChain`.
96+
pub unsafe fn add_chain(
97+
&mut self,
98+
mem: &GuestMemoryMmap,
99+
head: DescriptorChain,
100+
) -> Result<(), RxBufferError> {
101+
let head_index = head.index;
102+
103+
let mut next_descriptor = Some(head);
104+
let mut chain_len: u16 = 0;
105+
while let Some(desc) = next_descriptor {
106+
if !desc.is_write_only() {
107+
self.iovecs.pop_back(usize::from(chain_len));
108+
return Err(RxBufferError::ReadOnlyDescriptor);
109+
}
110+
111+
// We use get_slice instead of `get_host_address` here in order to have the whole
112+
// range of the descriptor chain checked, i.e. [addr, addr + len) is a valid memory
113+
// region in the GuestMemoryMmap.
114+
let slice = match mem.get_slice(desc.addr, desc.len as usize) {
115+
Ok(slice) => slice,
116+
Err(e) => {
117+
self.iovecs.pop_back(usize::from(chain_len));
118+
return Err(RxBufferError::GuestMemory(e));
119+
}
120+
};
121+
122+
// We need to mark the area of guest memory that will be mutated through this
123+
// IoVecBufferMut as dirty ahead of time, as we loose access to all
124+
// vm-memory related information after converting down to iovecs.
125+
slice.bitmap().mark_dirty(0, desc.len as usize);
126+
127+
let iov_base = slice.ptr_guard_mut().as_ptr().cast::<c_void>();
128+
self.iovecs.push_back(iovec {
129+
iov_base,
130+
iov_len: desc.len as size_t,
131+
});
132+
chain_len += 1;
133+
134+
next_descriptor = desc.next_descriptor();
135+
}
136+
self.chain_infos.push_back(ChainInfo {
137+
head_index,
138+
chain_len,
139+
});
140+
141+
Ok(())
142+
}
143+
144+
/// Writes bytes from a slice into buffer.
145+
pub fn write(&mut self, mut bytes: &[u8]) -> Result<(), RxBufferError> {
146+
for iov in self.iovecs.as_mut_slice() {
147+
if bytes.is_empty() {
148+
break;
149+
}
150+
let iov_slice_len = bytes.len().min(iov.iov_len);
151+
// SAFETY: The user space pointer and the length were verified during
152+
// the iovec creation.
153+
let slice =
154+
unsafe { std::slice::from_raw_parts_mut(iov.iov_base.cast(), iov_slice_len) };
155+
slice.copy_from_slice(&bytes[0..slice.len()]);
156+
bytes = &bytes[slice.len()..];
157+
}
158+
if !bytes.is_empty() {
159+
Err(RxBufferError::WriteOverflow)
160+
} else {
161+
Ok(())
162+
}
163+
}
164+
165+
/// Finish packet processing by removing used iovecs from the buffer and
166+
/// writing information about used descriptor chains into the queue.
167+
///
168+
/// # Safety
169+
/// `RxBuffer` should not be empty when this method is called because it
170+
/// assumes there is at least one chain holding data.
171+
pub unsafe fn finish_packet(&mut self, bytes_written: u32, rx_queue: &mut Queue) {
172+
// This function is called only after some bytes were written to the
173+
// buffer. This means the iov_ring cannot be empty.
174+
debug_assert!(!self.iovecs.is_empty());
175+
let packet_head = self.iovecs.as_slice()[0].iov_base;
176+
177+
let iov_info = self
178+
.chain_infos
179+
.pop_front()
180+
.expect("This should never happen if write to the buffer succeded.");
181+
self.iovecs.pop_front(usize::from(iov_info.chain_len));
182+
183+
if let Err(err) = rx_queue.write_used_element(
184+
(rx_queue.next_used + Wrapping(self.used_descriptors)).0,
185+
iov_info.head_index,
186+
bytes_written,
187+
) {
188+
error!(
189+
"net: Failed to add used descriptor {} of length {} to RX queue: {err}",
190+
iov_info.head_index, bytes_written
191+
);
192+
}
193+
self.used_descriptors += 1;
194+
195+
// SAFETY: The user space pointer was verified at the point of creation.
196+
#[allow(clippy::transmute_ptr_to_ref)]
197+
let header: &mut virtio_net_hdr_v1 = unsafe { std::mem::transmute(packet_head) };
198+
header.num_buffers = 1;
199+
}
200+
201+
/// Notify queue about all descriptor chains we used to process packets so far.
202+
pub fn notify_queue(&mut self, rx_queue: &mut Queue) {
203+
rx_queue.advance_used_ring(self.used_descriptors);
204+
self.used_descriptors = 0;
205+
}
206+
}
207+
208+
#[cfg(test)]
209+
// TODO why are we going through this? why clippy hates everything?
210+
#[allow(clippy::cast_possible_wrap)]
211+
#[allow(clippy::needless_range_loop)]
212+
#[allow(clippy::cast_possible_truncation)]
213+
mod tests {
214+
use vm_memory::GuestAddress;
215+
216+
use super::*;
217+
use crate::devices::virtio::test_utils::{set_dtable_one_chain, VirtQueue};
218+
use crate::test_utils::single_region_mem;
219+
220+
#[test]
221+
fn test_rx_buffer_new() {
222+
let mut buff = RxBuffer::new().unwrap();
223+
assert!(buff.is_empty());
224+
assert_eq!(buff.one_chain_mut_slice(), &mut []);
225+
}
226+
227+
#[test]
228+
fn test_rx_buffer_add_chain() {
229+
let mem = single_region_mem(65562);
230+
let rxq = VirtQueue::new(GuestAddress(0), &mem, 256);
231+
let mut queue = rxq.create_queue();
232+
233+
// Single chain with len of 16
234+
{
235+
let chain_len = 16;
236+
set_dtable_one_chain(&rxq, chain_len);
237+
let desc = queue.pop().unwrap();
238+
239+
let mut buff = RxBuffer::new().unwrap();
240+
// SAFETY: safe it is a test memory
241+
unsafe {
242+
buff.add_chain(&mem, desc).unwrap();
243+
}
244+
let slice = buff.one_chain_mut_slice();
245+
for i in 0..chain_len {
246+
assert_eq!(
247+
slice[i].iov_base as u64,
248+
mem.get_host_address(GuestAddress((2048 + 1024 * i) as u64))
249+
.unwrap() as u64
250+
);
251+
assert_eq!(slice[i].iov_len, 1024);
252+
}
253+
assert_eq!(buff.chain_infos.len(), 1);
254+
assert_eq!(
255+
buff.chain_infos.items[0],
256+
ChainInfo {
257+
head_index: 0,
258+
chain_len: 16
259+
}
260+
);
261+
}
262+
}
263+
264+
#[test]
265+
#[should_panic]
266+
fn test_rx_buffer_write_panic() {
267+
let mem = single_region_mem(65562);
268+
let rxq = VirtQueue::new(GuestAddress(0), &mem, 256);
269+
let mut queue = rxq.create_queue();
270+
271+
set_dtable_one_chain(&rxq, 1);
272+
let desc = queue.pop().unwrap();
273+
274+
let mut buff = RxBuffer::new().unwrap();
275+
// SAFETY: safe it is a test memory
276+
unsafe {
277+
buff.add_chain(&mem, desc).unwrap();
278+
}
279+
280+
// Write should panic, because we unwrap on error
281+
// because we try to write more than buffer can hold.
282+
buff.write(&[69; 2 * 1024]).unwrap();
283+
}
284+
285+
#[test]
286+
fn test_rx_buffer_write() {
287+
let mem = single_region_mem(65562);
288+
let rxq = VirtQueue::new(GuestAddress(0), &mem, 256);
289+
let mut queue = rxq.create_queue();
290+
291+
set_dtable_one_chain(&rxq, 1);
292+
let desc = queue.pop().unwrap();
293+
294+
let mut buff = RxBuffer::new().unwrap();
295+
// SAFETY: safe it is a test memory
296+
unsafe {
297+
buff.add_chain(&mem, desc).unwrap();
298+
}
299+
300+
// Initially data should be all zeros
301+
let slice = buff.one_chain_mut_slice();
302+
let data_slice_before: &[u8] =
303+
// SAFETY: safe as iovecs are verified on creation
304+
unsafe { std::slice::from_raw_parts(slice[0].iov_base.cast(), slice[0].iov_len) };
305+
assert_eq!(data_slice_before, &[0; 1024]);
306+
307+
// Write should happen to first iovec (as there is only 1)
308+
buff.write(&[69; 1024]).unwrap();
309+
310+
let slice = buff.one_chain_mut_slice();
311+
let data_slice_after: &[u8] =
312+
// SAFETY: safe as iovecs are verified on creation
313+
unsafe { std::slice::from_raw_parts(slice[0].iov_base.cast(), slice[0].iov_len) };
314+
assert_eq!(data_slice_after, &[69; 1024]);
315+
}
316+
317+
#[test]
318+
fn test_rx_buffer_finish_packet_and_notify() {
319+
let mem = single_region_mem(65562);
320+
let rxq = VirtQueue::new(GuestAddress(0), &mem, 256);
321+
let mut queue = rxq.create_queue();
322+
323+
let chain_len = 16;
324+
set_dtable_one_chain(&rxq, chain_len);
325+
let desc = queue.pop().unwrap();
326+
327+
let mut buff = RxBuffer::new().unwrap();
328+
// SAFETY: safe it is a test memory
329+
unsafe {
330+
buff.add_chain(&mem, desc).unwrap();
331+
}
332+
333+
let slice = buff.one_chain_mut_slice();
334+
// SAFETY: The user space pointer was verified at the point of creation.
335+
#[allow(clippy::transmute_ptr_to_ref)]
336+
let header: &virtio_net_hdr_v1 = unsafe { std::mem::transmute(slice[0].iov_base) };
337+
assert_eq!(header.num_buffers, 0);
338+
339+
// There is one chain in the buffer. The length of the data "written" does
340+
// not really matter. We just need to check that single chain present was popped
341+
// and number of buffers is correctly set in the header.
342+
// SAFETY: the buff is not empty
343+
unsafe {
344+
buff.finish_packet(1024, &mut queue);
345+
}
346+
assert_eq!(buff.iovecs.len(), 0);
347+
assert!(buff.is_empty());
348+
assert_eq!(header.num_buffers, 1);
349+
350+
assert_eq!(buff.used_descriptors, 1);
351+
assert_eq!(rxq.used.idx.get(), 0);
352+
buff.notify_queue(&mut queue);
353+
assert_eq!(buff.used_descriptors, 0);
354+
assert_eq!(rxq.used.idx.get(), 1);
355+
}
356+
}

0 commit comments

Comments
 (0)