Skip to content

Commit b47c656

Browse files
vhost: add support for packed virtqueues
Implement packed virtqueue support to improve performance through better cache locality and reduced memory overhead compared to split virtqueues. This addresses the VirtIO specification. Changes: - Add shared vring flag constants in backend.rs for architecture independence - Support packed virtqueue detection in both vhost-kern and vhost-user - Implement proper feature classification (device vs transport features) - Add validation for packed ring memory layout - Reduce code duplication in validation logic - Add test coverage for packed virtqueue functionality Signed-off-by: Prakash Shekhar <[email protected]>
1 parent f9b25c2 commit b47c656

File tree

4 files changed

+209
-28
lines changed

4 files changed

+209
-28
lines changed

vhost/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [Unreleased]
44

55
### Added
6+
- [[#301]](https://github.com/rust-vmm/vhost/pull/301) Add support for packed virtqueues in vhost-user and vhost-kern backends
67
### Changed
78
### Deprecated
89
### Fixed

vhost/src/backend.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,27 @@ use super::{Error, Result};
2424
/// Maximum number of memory regions supported.
2525
pub const VHOST_MAX_MEMORY_REGIONS: usize = 255;
2626

27+
/// Vring flags used in vhost backends.
28+
pub mod vring_flags {
29+
/// Support log of vring operations.
30+
/// Modifications to "used" vring should be logged.
31+
pub const VHOST_VRING_F_LOG: u32 = 0x1;
32+
/// Indicates packed virtqueue format.
33+
/// When set, the vring uses packed layout instead of split layout.
34+
pub const VHOST_VRING_F_PACKED: u32 = 0x2;
35+
}
36+
2737
/// Vring configuration data.
38+
///
39+
/// For split virtqueues (traditional layout):
40+
/// - `desc_table_addr`: Descriptor table address
41+
/// - `used_ring_addr`: Used ring buffer address
42+
/// - `avail_ring_addr`: Available ring buffer address
43+
///
44+
/// For packed virtqueues (when VHOST_VRING_F_PACKED flag is set):
45+
/// - `desc_table_addr`: Packed descriptor ring address
46+
/// - `used_ring_addr`: Driver event suppression structure address
47+
/// - `avail_ring_addr`: Device event suppression structure address
2848
#[derive(Default, Clone, Copy)]
2949
pub struct VringConfigData {
3050
/// Maximum queue size supported by the driver.
@@ -33,11 +53,11 @@ pub struct VringConfigData {
3353
pub queue_size: u16,
3454
/// Bitmask of vring flags.
3555
pub flags: u32,
36-
/// Descriptor table address.
56+
/// Descriptor table address (split) / Packed descriptor ring address (packed).
3757
pub desc_table_addr: u64,
38-
/// Used ring buffer address.
58+
/// Used ring buffer address (split) / Driver event suppression address (packed).
3959
pub used_ring_addr: u64,
40-
/// Available ring buffer address.
60+
/// Available ring buffer address (split) / Device event suppression address (packed).
4161
pub avail_ring_addr: u64,
4262
/// Optional address for logging.
4363
pub log_addr: Option<u64>,

vhost/src/vhost_kern/mod.rs

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use vmm_sys_util::ioctl::{ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_
2323
use super::{
2424
Error, Result, VhostAccess, VhostBackend, VhostIotlbBackend, VhostIotlbMsg,
2525
VhostIotlbMsgParser, VhostIotlbType, VhostUserDirtyLogRegion, VhostUserMemoryRegionInfo,
26-
VringConfigData, VHOST_MAX_MEMORY_REGIONS,
26+
VringConfigData, VHOST_MAX_MEMORY_REGIONS, vring_flags,
2727
};
2828

2929
pub mod vhost_binding;
@@ -54,6 +54,49 @@ fn io_result<T>(rc: isize, res: T) -> Result<T> {
5454
}
5555
}
5656

57+
/// Check if the configuration data indicates packed virtqueue format.
58+
fn is_packed_vring(config_data: &VringConfigData) -> bool {
59+
config_data.flags & vring_flags::VHOST_VRING_F_PACKED != 0
60+
}
61+
62+
/// Helper function to validate that a memory region is accessible.
63+
fn validate_memory_region<M: GuestMemory>(
64+
addr: u64,
65+
size: GuestUsize,
66+
memory: &M,
67+
) -> bool {
68+
GuestAddress(addr)
69+
.checked_add(size)
70+
.map_or(false, |end_addr| memory.address_in_range(end_addr))
71+
}
72+
73+
/// Validate virtqueue memory layout for both packed and split formats.
74+
fn validate_vring_memory<AS: GuestAddressSpace>(
75+
config_data: &VringConfigData,
76+
queue_size: u16,
77+
memory: &AS::M,
78+
is_packed: bool,
79+
) -> bool {
80+
if is_packed {
81+
// Packed ring: single descriptor ring + event suppression structures
82+
let desc_ring_size = 16 * u64::from(queue_size) as GuestUsize;
83+
let event_size = 4; // 4 bytes for event suppression structures
84+
85+
validate_memory_region(config_data.desc_table_addr, desc_ring_size, memory)
86+
&& validate_memory_region(config_data.avail_ring_addr, event_size, memory)
87+
&& validate_memory_region(config_data.used_ring_addr, event_size, memory)
88+
} else {
89+
// Split ring: separate descriptor, available, and used rings
90+
let desc_table_size = 16 * u64::from(queue_size) as GuestUsize;
91+
let avail_ring_size = 6 + 2 * u64::from(queue_size) as GuestUsize;
92+
let used_ring_size = 6 + 8 * u64::from(queue_size) as GuestUsize;
93+
94+
validate_memory_region(config_data.desc_table_addr, desc_table_size, memory)
95+
&& validate_memory_region(config_data.avail_ring_addr, avail_ring_size, memory)
96+
&& validate_memory_region(config_data.used_ring_addr, used_ring_size, memory)
97+
}
98+
}
99+
57100
/// Represent an in-kernel vhost device backend.
58101
pub trait VhostKernBackend: AsRawFd {
59102
/// Associated type to access guest memory.
@@ -73,25 +116,10 @@ pub trait VhostKernBackend: AsRawFd {
73116
}
74117

75118
let m = self.mem().memory();
76-
let desc_table_size = 16 * u64::from(queue_size) as GuestUsize;
77-
let avail_ring_size = 6 + 2 * u64::from(queue_size) as GuestUsize;
78-
let used_ring_size = 6 + 8 * u64::from(queue_size) as GuestUsize;
79-
if GuestAddress(config_data.desc_table_addr)
80-
.checked_add(desc_table_size)
81-
.is_none_or(|v| !m.address_in_range(v))
82-
{
83-
return false;
84-
}
85-
if GuestAddress(config_data.avail_ring_addr)
86-
.checked_add(avail_ring_size)
87-
.is_none_or(|v| !m.address_in_range(v))
88-
{
89-
return false;
90-
}
91-
if GuestAddress(config_data.used_ring_addr)
92-
.checked_add(used_ring_size)
93-
.is_none_or(|v| !m.address_in_range(v))
94-
{
119+
120+
// Validate vring memory layout based on format
121+
let is_packed = is_packed_vring(config_data);
122+
if !validate_vring_memory::<Self::AS>(config_data, queue_size, &m, is_packed) {
95123
return false;
96124
}
97125

@@ -466,3 +494,29 @@ impl VringConfigData {
466494
})
467495
}
468496
}
497+
498+
#[cfg(test)]
499+
mod tests {
500+
use super::*;
501+
502+
#[test]
503+
fn test_packed_vring_detection() {
504+
// Test that packed vring detection works independently of vhost-user feature
505+
let mut config = VringConfigData::default();
506+
507+
// Test split virtqueue (default)
508+
assert!(!is_packed_vring(&config));
509+
510+
// Test packed virtqueue
511+
config.flags = vring_flags::VHOST_VRING_F_PACKED;
512+
assert!(is_packed_vring(&config));
513+
514+
// Test with log flag combined
515+
config.flags = vring_flags::VHOST_VRING_F_LOG | vring_flags::VHOST_VRING_F_PACKED;
516+
assert!(is_packed_vring(&config));
517+
518+
// Test with only log flag
519+
config.flags = vring_flags::VHOST_VRING_F_LOG;
520+
assert!(!is_packed_vring(&config));
521+
}
522+
}

vhost/src/vhost_user/message.rs

Lines changed: 111 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -381,11 +381,19 @@ impl<T: Req> VhostUserMsgValidator for VhostUserMsgHeader<T> {
381381
bitflags! {
382382
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
383383
/// Transport specific flags in VirtIO feature set defined by vhost-user.
384+
///
385+
/// NOTE: This is for vhost-user transport-specific features only.
386+
/// Standard VirtIO device features (like VIRTIO_F_RING_PACKED) should be
387+
/// negotiated through the normal VirtIO device feature mechanism, not here.
384388
pub struct VhostUserVirtioFeatures: u64 {
385389
/// Log dirtied shared memory pages.
386390
const LOG_ALL = 0x400_0000;
387391
/// Feature flag for the protocol feature.
388392
const PROTOCOL_FEATURES = 0x4000_0000;
393+
// NOTE: VIRTIO_F_RING_PACKED was incorrectly placed here. It's a standard
394+
// VirtIO device feature and should be negotiated through device features,
395+
// not vhost-user transport features. Packed virtqueue configuration is
396+
// handled through VhostUserVringAddrFlags::VHOST_VRING_F_PACKED.
389397
}
390398
}
391399

@@ -708,11 +716,23 @@ impl VhostUserMsgValidator for VhostUserVringState {}
708716

709717
// Bit mask for vring address flags.
710718
bitflags! {
711-
/// Flags for vring address.
719+
/// Flags for vring address configuration.
720+
///
721+
/// These flags control vring setup and are used AFTER device feature negotiation.
722+
/// The proper flow is:
723+
/// 1. Device features (like VIRTIO_F_RING_PACKED) are negotiated through VirtIO standard mechanism
724+
/// 2. These flags configure the actual vring layout based on negotiated features
725+
///
726+
/// NOTE: These values must match the constants in crate::backend::vring_flags
727+
/// to ensure compatibility between vhost-user and vhost-kern backends.
712728
pub struct VhostUserVringAddrFlags: u32 {
713729
/// Support log of vring operations.
714730
/// Modifications to "used" vring should be logged.
715731
const VHOST_VRING_F_LOG = 0x1;
732+
/// Indicates packed virtqueue format.
733+
/// When set, the vring uses packed layout instead of split layout.
734+
/// This should only be set if VIRTIO_F_RING_PACKED was negotiated as a device feature.
735+
const VHOST_VRING_F_PACKED = 0x2;
716736
}
717737
}
718738

@@ -777,13 +797,31 @@ impl VhostUserMsgValidator for VhostUserVringAddr {
777797
fn is_valid(&self) -> bool {
778798
if (self.flags & !VhostUserVringAddrFlags::all().bits()) != 0 {
779799
return false;
780-
} else if self.descriptor & 0xf != 0 {
781-
return false;
782-
} else if self.available & 0x1 != 0 {
800+
}
801+
802+
// Common validation for both packed and split rings
803+
// Descriptor table must be 16-byte aligned for both formats
804+
if self.descriptor & 0xf != 0 {
783805
return false;
784-
} else if self.used & 0x3 != 0 {
806+
}
807+
// Used ring must be 4-byte aligned for both formats
808+
if self.used & 0x3 != 0 {
785809
return false;
786810
}
811+
812+
// Available ring alignment depends on format
813+
let is_packed = self.flags & VhostUserVringAddrFlags::VHOST_VRING_F_PACKED.bits() != 0;
814+
if is_packed {
815+
// Packed: available ring (device event suppression) must be 4-byte aligned
816+
if self.available & 0x3 != 0 {
817+
return false;
818+
}
819+
} else {
820+
// Split: available ring must be 2-byte aligned
821+
if self.available & 0x1 != 0 {
822+
return false;
823+
}
824+
}
787825
true
788826
}
789827
}
@@ -1417,4 +1455,72 @@ mod tests {
14171455
msg.flags |= 0x4;
14181456
assert!(!msg.is_valid());
14191457
}
1458+
1459+
#[test]
1460+
fn test_packed_virtqueue_vring_flags() {
1461+
// Test vring address flags for packed virtqueue configuration
1462+
// NOTE: VIRTIO_F_RING_PACKED device feature negotiation happens separately
1463+
1464+
let a = VhostUserVringAddrFlags::VHOST_VRING_F_PACKED.bits();
1465+
assert_eq!(a, 0x2);
1466+
1467+
let combined = VhostUserVringAddrFlags::VHOST_VRING_F_LOG
1468+
| VhostUserVringAddrFlags::VHOST_VRING_F_PACKED;
1469+
let a = combined.bits();
1470+
assert_eq!(a, 0x3);
1471+
}
1472+
1473+
#[test]
1474+
fn test_packed_vring_addr_validation() {
1475+
let mut addr = VhostUserVringAddr::new(
1476+
0,
1477+
VhostUserVringAddrFlags::VHOST_VRING_F_PACKED,
1478+
0x1000,
1479+
0x2000,
1480+
0x3000,
1481+
0x4000,
1482+
);
1483+
1484+
let a = addr.index;
1485+
assert_eq!(a, 0);
1486+
let a = addr.flags;
1487+
assert_eq!(a, VhostUserVringAddrFlags::VHOST_VRING_F_PACKED.bits());
1488+
let a = addr.descriptor;
1489+
assert_eq!(a, 0x1000);
1490+
let a = addr.used;
1491+
assert_eq!(a, 0x2000);
1492+
let a = addr.available;
1493+
assert_eq!(a, 0x3000);
1494+
let a = addr.log;
1495+
assert_eq!(a, 0x4000);
1496+
assert!(addr.is_valid());
1497+
1498+
addr.descriptor = 0x1001;
1499+
assert!(!addr.is_valid());
1500+
addr.descriptor = 0x1000;
1501+
1502+
addr.available = 0x3001;
1503+
assert!(!addr.is_valid());
1504+
addr.available = 0x3000;
1505+
1506+
addr.used = 0x2001;
1507+
assert!(!addr.is_valid());
1508+
addr.used = 0x2000;
1509+
assert!(addr.is_valid());
1510+
}
1511+
1512+
#[test]
1513+
fn test_vring_flags_compatibility() {
1514+
// Ensure vhost-user flags match the shared backend constants
1515+
use crate::backend::vring_flags;
1516+
1517+
assert_eq!(
1518+
VhostUserVringAddrFlags::VHOST_VRING_F_LOG.bits(),
1519+
vring_flags::VHOST_VRING_F_LOG
1520+
);
1521+
assert_eq!(
1522+
VhostUserVringAddrFlags::VHOST_VRING_F_PACKED.bits(),
1523+
vring_flags::VHOST_VRING_F_PACKED
1524+
);
1525+
}
14201526
}

0 commit comments

Comments
 (0)