Skip to content

Commit f80fbc5

Browse files
committed
feat: update IovDeque to support arbitrary size and host page size
Remove restriction on size and host page size. Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent 9f14de0 commit f80fbc5

File tree

1 file changed

+37
-20
lines changed

1 file changed

+37
-20
lines changed

src/vmm/src/devices/virtio/iov_deque.rs

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,7 @@ pub enum IovDequeError {
7777
// pub iov_len: ::size_t,
7878
// }
7979
// ```
80-
//
81-
// This value must be a multiple of 256 because this is the maximum number of `iovec` can fit into
82-
// 1 memory page: 256 * sizeof(iovec) == 4096 == HOST_PAGE_SIZE. IovDeque only operates with
83-
// `HOST_PAGE_SIZE` granularity.
80+
8481
#[derive(Debug)]
8582
pub struct IovDeque<const L: u16> {
8683
pub iov: *mut libc::iovec,
@@ -92,17 +89,15 @@ pub struct IovDeque<const L: u16> {
9289
unsafe impl<const L: u16> Send for IovDeque<L> {}
9390

9491
impl<const L: u16> IovDeque<L> {
95-
const BYTES: usize = L as usize * std::mem::size_of::<iovec>();
96-
9792
/// Create a [`memfd`] object that represents a single physical page
98-
fn create_memfd() -> Result<memfd::Memfd, IovDequeError> {
93+
fn create_memfd(pages_bytes: usize) -> Result<memfd::Memfd, IovDequeError> {
9994
// Create a sealable memfd.
10095
let opts = memfd::MemfdOptions::default().allow_sealing(true);
10196
let mfd = opts.create("iov_deque")?;
10297

10398
// Resize to system page size.
10499
mfd.as_file()
105-
.set_len(Self::BYTES.try_into().unwrap())
100+
.set_len(pages_bytes.try_into().unwrap())
106101
.map_err(IovDequeError::MemfdResize)?;
107102

108103
// Add seals to prevent further resizing.
@@ -135,13 +130,13 @@ impl<const L: u16> IovDeque<L> {
135130

136131
/// Allocate memory for our ring buffer
137132
///
138-
/// This will allocate 2 * `Self::BYTES` bytes of virtual memory.
139-
fn allocate_ring_buffer_memory() -> Result<*mut c_void, IovDequeError> {
133+
/// This will allocate 2 * `pages_bytes` bytes of virtual memory.
134+
fn allocate_ring_buffer_memory(pages_bytes: usize) -> Result<*mut c_void, IovDequeError> {
140135
// SAFETY: We are calling the system call with valid arguments
141136
unsafe {
142137
Self::mmap(
143138
std::ptr::null_mut(),
144-
Self::BYTES * 2,
139+
pages_bytes * 2,
145140
libc::PROT_NONE,
146141
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
147142
-1,
@@ -150,20 +145,29 @@ impl<const L: u16> IovDeque<L> {
150145
}
151146
}
152147

148+
/// Calculate a number of bytes in full pages required for
149+
/// the type to operate.
150+
fn pages_bytes() -> usize {
151+
let host_page_size = host_page_size();
152+
let bytes = L as usize * std::mem::size_of::<iovec>();
153+
let num_host_pages = bytes.div_ceil(host_page_size);
154+
num_host_pages * host_page_size
155+
}
156+
153157
/// Create a new [`IovDeque`] that can hold memory described by a single VirtIO queue.
154158
pub fn new() -> Result<Self, IovDequeError> {
155-
assert!(Self::BYTES % host_page_size() == 0);
159+
let pages_bytes = Self::pages_bytes();
156160

157-
let memfd = Self::create_memfd()?;
161+
let memfd = Self::create_memfd(pages_bytes)?;
158162
let raw_memfd = memfd.as_file().as_raw_fd();
159-
let buffer = Self::allocate_ring_buffer_memory()?;
163+
let buffer = Self::allocate_ring_buffer_memory(pages_bytes)?;
160164

161165
// Map the first page of virtual memory to the physical page described by the memfd object
162166
// SAFETY: We are calling the system call with valid arguments
163167
let _ = unsafe {
164168
Self::mmap(
165169
buffer,
166-
Self::BYTES,
170+
pages_bytes,
167171
libc::PROT_READ | libc::PROT_WRITE,
168172
libc::MAP_SHARED | libc::MAP_FIXED,
169173
raw_memfd,
@@ -174,17 +178,17 @@ impl<const L: u16> IovDeque<L> {
174178
// Map the second page of virtual memory to the physical page described by the memfd object
175179
//
176180
// SAFETY: This is safe because:
177-
// * Both `buffer` and the result of `buffer.add(Self::BYTES)` are within bounds of the
181+
// * Both `buffer` and the result of `buffer.add(pages_bytes)` are within bounds of the
178182
// allocation we got from `Self::allocate_ring_buffer_memory`.
179183
// * The resulting pointer is the beginning of the second page of our allocation, so it
180184
// doesn't wrap around the address space.
181-
let next_page = unsafe { buffer.add(Self::BYTES) };
185+
let next_page = unsafe { buffer.add(pages_bytes) };
182186

183187
// SAFETY: We are calling the system call with valid arguments
184188
let _ = unsafe {
185189
Self::mmap(
186190
next_page,
187-
Self::BYTES,
191+
pages_bytes,
188192
libc::PROT_READ | libc::PROT_WRITE,
189193
libc::MAP_SHARED | libc::MAP_FIXED,
190194
raw_memfd,
@@ -312,9 +316,10 @@ impl<const L: u16> IovDeque<L> {
312316

313317
impl<const L: u16> Drop for IovDeque<L> {
314318
fn drop(&mut self) {
319+
let pages_bytes = Self::pages_bytes();
315320
// SAFETY: We are passing an address that we got from a previous allocation of `2 *
316-
// Self::BYTES` bytes by calling mmap
317-
let _ = unsafe { libc::munmap(self.iov.cast(), Self::BYTES * 2) };
321+
// pages_bytes` by calling mmap
322+
let _ = unsafe { libc::munmap(self.iov.cast(), 2 * pages_bytes) };
318323
}
319324
}
320325

@@ -332,6 +337,18 @@ mod tests {
332337
assert_eq!(deque.len(), 0);
333338
}
334339

340+
#[test]
341+
fn test_new_less_than_page() {
342+
let deque = super::IovDeque::<128>::new().unwrap();
343+
assert_eq!(deque.len(), 0);
344+
}
345+
346+
#[test]
347+
fn test_new_more_than_page() {
348+
let deque = super::IovDeque::<512>::new().unwrap();
349+
assert_eq!(deque.len(), 0);
350+
}
351+
335352
fn make_iovec(id: u16, len: u16) -> iovec {
336353
iovec {
337354
iov_base: id as *mut libc::c_void,

0 commit comments

Comments
 (0)