Skip to content

Commit f3c5f1f

Browse files
committed
refactor(virtio): more descriptive used ring usage calculations
Replace offset calculations with hard coded values with more descriptive `std::mem::sizeof` with better comments of why the offset should be calculated this way. Additionally replace setting used element fields one by one with single call to `write_obj`. Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent 0152915 commit f3c5f1f

File tree

2 files changed

+94
-25
lines changed

2 files changed

+94
-25
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ pub(crate) fn inject_tap_tx_frame(net: &Net, len: usize) -> Vec<u8> {
312312
pub fn write_element_in_queue(net: &Net, idx: u16, val: u64) -> Result<(), DeviceError> {
313313
if idx as usize > net.queue_evts.len() {
314314
return Err(DeviceError::QueueError(QueueError::DescIndexOutOfBounds(
315-
idx,
315+
u32::from(idx),
316316
)));
317317
}
318318
net.queue_evts[idx as usize].write(val).unwrap();
@@ -322,7 +322,7 @@ pub fn write_element_in_queue(net: &Net, idx: u16, val: u64) -> Result<(), Devic
322322
pub fn get_element_from_queue(net: &Net, idx: u16) -> Result<u64, DeviceError> {
323323
if idx as usize > net.queue_evts.len() {
324324
return Err(DeviceError::QueueError(QueueError::DescIndexOutOfBounds(
325-
idx,
325+
u32::from(idx),
326326
)));
327327
}
328328
Ok(u64::try_from(net.queue_evts[idx as usize].as_raw_fd()).unwrap())

src/vmm/src/devices/virtio/queue.rs

Lines changed: 92 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use std::cmp::min;
99
use std::num::Wrapping;
1010
use std::sync::atomic::{fence, Ordering};
1111

12+
use utils::usize_to_u64;
13+
1214
use crate::logger::error;
1315
use crate::vstate::memory::{
1416
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap,
@@ -31,12 +33,15 @@ pub(super) const FIRECRACKER_MAX_QUEUE_SIZE: u16 = 256;
3133
#[derive(Debug, thiserror::Error, displaydoc::Display)]
3234
pub enum QueueError {
3335
/// Descriptor index out of bounds: {0}.
34-
DescIndexOutOfBounds(u16),
36+
DescIndexOutOfBounds(u32),
3537
/// Failed to write value into the virtio queue used ring: {0}
3638
UsedRing(#[from] vm_memory::GuestMemoryError),
3739
}
3840

3941
/// A virtio descriptor constraints with C representative.
42+
/// Taken from Virtio spec:
43+
/// https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-430008
44+
/// 2.6.5 The Virtqueue Descriptor Table
4045
#[repr(C)]
4146
#[derive(Default, Clone, Copy)]
4247
struct Descriptor {
@@ -49,6 +54,20 @@ struct Descriptor {
4954
// SAFETY: `Descriptor` is a POD and contains no padding.
5055
unsafe impl ByteValued for Descriptor {}
5156

57+
/// A virtio used element in the used ring.
58+
/// Taken from Virtio spec:
59+
/// https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-430008
60+
/// 2.6.8 The Virtqueue Used Ring
61+
#[repr(C)]
62+
#[derive(Default, Clone, Copy)]
63+
struct UsedElement {
64+
id: u32,
65+
len: u32,
66+
}
67+
68+
// SAFETY: `UsedElement` is a POD and contains no padding.
69+
unsafe impl ByteValued for UsedElement {}
70+
5271
/// A virtio descriptor chain.
5372
#[derive(Debug)]
5473
pub struct DescriptorChain<'a, M: GuestMemory = GuestMemoryMmap> {
@@ -430,31 +449,51 @@ impl Queue {
430449
) -> Result<(), QueueError> {
431450
debug_assert!(self.is_layout_valid(mem));
432451

433-
if desc_index >= self.actual_size() {
434-
error!(
435-
"attempted to add out of bounds descriptor to used ring: {}",
436-
desc_index
437-
);
438-
return Err(QueueError::DescIndexOutOfBounds(desc_index));
439-
}
440-
441-
let used_ring = self.used_ring;
442-
let next_used = u64::from(self.next_used.0 % self.actual_size());
443-
let used_elem = used_ring.unchecked_add(4 + next_used * 8);
444-
445-
mem.write_obj(u32::from(desc_index), used_elem)?;
446-
447-
let len_addr = used_elem.unchecked_add(4);
448-
mem.write_obj(len, len_addr)?;
452+
let next_used = self.next_used.0 % self.actual_size();
453+
let used_element = UsedElement {
454+
id: u32::from(desc_index),
455+
len,
456+
};
457+
self.write_used_ring(mem, next_used, used_element)?;
449458

450459
self.num_added += Wrapping(1);
451460
self.next_used += Wrapping(1);
452461

453462
// This fence ensures all descriptor writes are visible before the index update is.
454463
fence(Ordering::Release);
455464

456-
let next_used_addr = used_ring.unchecked_add(2);
457-
mem.write_obj(self.next_used.0, next_used_addr)
465+
self.set_next_used(self.next_used.0, mem);
466+
Ok(())
467+
}
468+
469+
fn write_used_ring<M: GuestMemory>(
470+
&self,
471+
mem: &M,
472+
index: u16,
473+
used_element: UsedElement,
474+
) -> Result<(), QueueError> {
475+
if used_element.id >= u32::from(self.actual_size()) {
476+
error!(
477+
"attempted to add out of bounds descriptor to used ring: {}",
478+
used_element.id
479+
);
480+
return Err(QueueError::DescIndexOutOfBounds(used_element.id));
481+
}
482+
483+
// Used ring has layout:
484+
// struct UsedRing {
485+
// flags: u16,
486+
// idx: u16,
487+
// ring: [UsedElement; <queue size>],
488+
// avail_event: u16,
489+
// }
490+
// We calculate offset into `ring` field.
491+
let used_ring_offset = std::mem::size_of::<u16>()
492+
+ std::mem::size_of::<u16>()
493+
+ std::mem::size_of::<UsedElement>() * usize::from(index);
494+
let used_element_address = self.used_ring.unchecked_add(usize_to_u64(used_ring_offset));
495+
496+
mem.write_obj(used_element, used_element_address)
458497
.map_err(QueueError::UsedRing)
459498
}
460499

@@ -484,15 +523,45 @@ impl Queue {
484523
Wrapping(mem.read_obj::<u16>(used_event_addr).unwrap())
485524
}
486525

487-
/// Helper method that writes `val` to the `avail_event` field of the used ring.
488-
fn set_avail_event<M: GuestMemory>(&mut self, val: u16, mem: &M) {
526+
/// Helper method that writes to the `avail_event` field of the used ring.
527+
#[inline(always)]
528+
fn set_avail_event<M: GuestMemory>(&mut self, avail_event: u16, mem: &M) {
489529
debug_assert!(self.is_layout_valid(mem));
490530

531+
// Used ring has layout:
532+
// struct UsedRing {
533+
// flags: u16,
534+
// idx: u16,
535+
// ring: [UsedElement; <queue size>],
536+
// avail_event: u16,
537+
// }
538+
// We calculate offset into `avail_event` field.
539+
let avail_event_offset = std::mem::size_of::<u16>()
540+
+ std::mem::size_of::<u16>()
541+
+ std::mem::size_of::<UsedElement>() * usize::from(self.actual_size());
491542
let avail_event_addr = self
492543
.used_ring
493-
.unchecked_add(u64::from(4 + 8 * self.actual_size()));
544+
.unchecked_add(usize_to_u64(avail_event_offset));
545+
546+
mem.write_obj(avail_event, avail_event_addr).unwrap();
547+
}
494548

495-
mem.write_obj(val, avail_event_addr).unwrap();
549+
/// Helper method that writes to the `idx` field of the used ring.
550+
#[inline(always)]
551+
fn set_next_used<M: GuestMemory>(&mut self, next_used: u16, mem: &M) {
552+
debug_assert!(self.is_layout_valid(mem));
553+
554+
// Used ring has layout:
555+
// struct UsedRing {
556+
// flags: u16,
557+
// idx: u16,
558+
// ring: [UsedElement; <queue size>],
559+
// avail_event: u16,
560+
// }
561+
// We calculate offset into `idx` field.
562+
let idx_offset = std::mem::size_of::<u16>();
563+
let next_used_addr = self.used_ring.unchecked_add(usize_to_u64(idx_offset));
564+
mem.write_obj(next_used, next_used_addr).unwrap();
496565
}
497566

498567
/// Try to enable notification events from the guest driver. Returns true if notifications were

0 commit comments

Comments
 (0)