Skip to content

Commit eb683be

Browse files
committed
net: create IoVecBuffer type for vectored writes
The standard library handles vectored writes and reads using the `IoSlice` and `IoSliceMut` respectively. We introduce a new type, `IoVecBuffer` which essentially is a vector of `IoSlice` and is able to be instantiated from a `DescriptorChain`. `IoVecBuffer` provides us the necessary bits for using `write_vectored` to transmit packets in the tap. Signed-off-by: Babis Chalios <[email protected]>
1 parent e97bf9f commit eb683be

File tree

2 files changed

+226
-0
lines changed

2 files changed

+226
-0
lines changed
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
//
4+
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
5+
// Use of this source code is governed by a BSD-style license that can be
6+
// found in the THIRD-PARTY file.
7+
8+
use crate::virtio::DescriptorChain;
9+
use std::io::IoSlice;
10+
use std::ops::Deref;
11+
use vm_memory::{GuestMemory, GuestMemoryError, GuestMemoryMmap};
12+
13+
#[derive(Debug)]
14+
pub enum Error {
15+
/// We found a write-only descriptor where read-only was expected
16+
WriteOnlyDescriptor,
17+
/// An error happened with guest memory handling
18+
GuestMemory(vm_memory::GuestMemoryError),
19+
}
20+
21+
impl From<GuestMemoryError> for Error {
22+
fn from(err: GuestMemoryError) -> Self {
23+
Error::GuestMemory(err)
24+
}
25+
}
26+
27+
type Result<T> = std::result::Result<T, Error>;
28+
29+
/// This is essentially a wrapper of a `Vec<IoSlice>` which can be passed `writev`.
30+
///
31+
/// It describes a buffer passed to us by the guest that is scattered across multiple
32+
/// memory regions. Additionally, this wrapper provides methods that allow reading arbitrary ranges
33+
/// of data from that buffer.
34+
#[derive(Debug)]
35+
pub(crate) struct IoVecBuffer<'a> {
36+
// container of the memory regions included in this IO vector
37+
vecs: Vec<IoSlice<'a>>,
38+
// Total length of the IoVecBuffer
39+
len: usize,
40+
}
41+
42+
impl<'a> Deref for IoVecBuffer<'a> {
43+
type Target = [IoSlice<'a>];
44+
45+
fn deref(&self) -> &Self::Target {
46+
self.vecs.as_slice()
47+
}
48+
}
49+
50+
#[cfg(test)]
51+
impl<'a> From<&'a [u8]> for IoVecBuffer<'a> {
52+
fn from(buf: &'a [u8]) -> Self {
53+
Self {
54+
vecs: vec![IoSlice::new(buf)],
55+
len: buf.len(),
56+
}
57+
}
58+
}
59+
60+
impl<'a> IoVecBuffer<'a> {
61+
/// Create an `IoVecBuffer` from a `DescriptorChain`
62+
pub fn from_descriptor_chain(mem: &'a GuestMemoryMmap, head: DescriptorChain) -> Result<Self> {
63+
let mut vecs = vec![];
64+
let mut len = 0usize;
65+
66+
let mut next_descriptor = Some(head);
67+
while let Some(desc) = next_descriptor {
68+
if desc.is_write_only() {
69+
return Err(Error::WriteOnlyDescriptor);
70+
}
71+
72+
// This is safe since we get `ptr` from `get_slice` which also checks the length
73+
// of the descriptor
74+
let ptr = mem.get_slice(desc.addr, desc.len as usize)?.as_ptr();
75+
let slice = unsafe { std::slice::from_raw_parts(ptr, desc.len as usize) };
76+
vecs.push(IoSlice::new(slice));
77+
len += desc.len as usize;
78+
79+
next_descriptor = desc.next_descriptor();
80+
}
81+
82+
Ok(Self { vecs, len })
83+
}
84+
85+
/// Get the total length of the memory regions covered by this `IoVecBuffer`
86+
pub fn len(&self) -> usize {
87+
self.len
88+
}
89+
90+
/// Reads a number of bytes from the `IoVecBuffer` starting at a given offset.
91+
///
92+
/// This will try to fill `buf` reading bytes from the `IoVecBuffer` starting from
93+
/// the given offset.
94+
///
95+
/// # Retruns
96+
///
97+
/// The number of bytes read (if any)
98+
pub fn read_at(&self, buf: &mut [u8], offset: u64) -> Option<usize> {
99+
// We can't read past the end of this `IoVecBuffer`
100+
if offset >= self.len() as u64 {
101+
return None;
102+
}
103+
104+
// We try to fill up `buf` with as many bytes as we have
105+
let size = std::cmp::min(buf.len(), self.len() - offset as usize);
106+
// This is the last byte of `self` that we will reado out
107+
let end = offset as usize + size;
108+
// byte index in `self`
109+
let mut seg_start = 0;
110+
// byte index in `buf`
111+
let mut buf_start = 0;
112+
113+
for seg in self.vecs.iter() {
114+
let seg_end = seg_start + seg.len();
115+
116+
let mut write_start = std::cmp::max(seg_start, offset as usize);
117+
let mut write_end = std::cmp::min(seg_end, end);
118+
119+
if write_start < write_end {
120+
write_start -= seg_start;
121+
write_end -= seg_start;
122+
let bytes = write_end - write_start;
123+
let buf_end = buf_start + bytes;
124+
buf[buf_start..buf_end].copy_from_slice(&seg[write_start..write_end]);
125+
buf_start += bytes;
126+
}
127+
128+
seg_start = seg_end;
129+
// The next segment is out of range, we are done here.
130+
if seg_start >= end {
131+
break;
132+
}
133+
}
134+
135+
Some(size)
136+
}
137+
}
138+
139+
#[cfg(test)]
140+
pub mod tests {
141+
use super::IoVecBuffer;
142+
use crate::virtio::queue::{Queue, VIRTQ_DESC_F_NEXT, VIRTQ_DESC_F_WRITE};
143+
use crate::virtio::test_utils::VirtQueue;
144+
use vm_memory::{test_utils::create_anon_guest_memory, Bytes, GuestAddress, GuestMemoryMmap};
145+
146+
fn chain(is_write_only: bool) -> (Queue, GuestMemoryMmap) {
147+
let m = create_anon_guest_memory(
148+
&[
149+
(GuestAddress(0), 0x10000),
150+
(GuestAddress(0x20000), 0x10000),
151+
(GuestAddress(0x40000), 0x10000),
152+
],
153+
false,
154+
)
155+
.unwrap();
156+
157+
let v: Vec<u8> = (0..=255).collect();
158+
m.write_slice(&v, GuestAddress(0x20000)).unwrap();
159+
160+
let vq = VirtQueue::new(GuestAddress(0), &m, 16);
161+
162+
let mut q = vq.create_queue();
163+
q.ready = true;
164+
165+
let flags = if is_write_only {
166+
VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE
167+
} else {
168+
VIRTQ_DESC_F_NEXT
169+
};
170+
171+
for j in 0..4 {
172+
vq.dtable[j].set(0x20000 + 64 * j as u64, 64, flags, (j + 1) as u16);
173+
}
174+
175+
// one chain: (0, 1, 2, 3)
176+
vq.dtable[3].flags.set(flags & !VIRTQ_DESC_F_NEXT);
177+
vq.avail.ring[0].set(0);
178+
vq.avail.idx.set(1);
179+
180+
(q, m)
181+
}
182+
183+
#[test]
184+
fn test_access_mode() {
185+
let (mut q, mem) = chain(false);
186+
let head = q.pop(&mem).unwrap();
187+
assert!(IoVecBuffer::from_descriptor_chain(&mem, head).is_ok());
188+
189+
let (mut q, mem) = chain(true);
190+
let head = q.pop(&mem).unwrap();
191+
assert!(IoVecBuffer::from_descriptor_chain(&mem, head).is_err());
192+
}
193+
194+
#[test]
195+
fn test_iovec_length() {
196+
let (mut q, mem) = chain(false);
197+
let head = q.pop(&mem).unwrap();
198+
199+
let iovec = IoVecBuffer::from_descriptor_chain(&mem, head).unwrap();
200+
assert_eq!(iovec.len(), 4 * 64);
201+
}
202+
203+
#[test]
204+
fn test_iovec_read_at() {
205+
let (mut q, mem) = chain(false);
206+
let head = q.pop(&mem).unwrap();
207+
208+
let iovec = IoVecBuffer::from_descriptor_chain(&mem, head).unwrap();
209+
210+
let mut buf = vec![0; 5];
211+
assert_eq!(iovec.read_at(&mut buf, 0), Some(5));
212+
assert_eq!(buf, vec![0u8, 1, 2, 3, 4]);
213+
214+
assert_eq!(iovec.read_at(&mut buf, 1), Some(5));
215+
assert_eq!(buf, vec![1u8, 2, 3, 4, 5]);
216+
217+
assert_eq!(iovec.read_at(&mut buf, 60), Some(5));
218+
assert_eq!(buf, vec![60u8, 61, 62, 63, 64]);
219+
220+
assert_eq!(iovec.read_at(&mut buf, 252), Some(4));
221+
assert_eq!(buf[0..4], vec![252u8, 253, 254, 255]);
222+
223+
assert_eq!(iovec.read_at(&mut buf, 256), None);
224+
}
225+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub const TX_INDEX: usize = 1;
1414

1515
pub mod device;
1616
pub mod event_handler;
17+
mod iovec;
1718
pub mod persist;
1819
mod tap;
1920
pub mod test_utils;

0 commit comments

Comments
 (0)