Skip to content

Add packed virtqueue support #301

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions vhost/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [Unreleased]

### Added
- Add support for packed virtqueues in vhost-user and vhost-kern backends
### Changed
### Deprecated
### Fixed
Expand Down
16 changes: 13 additions & 3 deletions vhost/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ use super::{Error, Result};
pub const VHOST_MAX_MEMORY_REGIONS: usize = 255;

/// Vring configuration data.
///
/// For split virtqueues (traditional layout):
/// - `desc_table_addr`: Descriptor table address
/// - `used_ring_addr`: Used ring buffer address
/// - `avail_ring_addr`: Available ring buffer address
///
/// For packed virtqueues (when VHOST_VRING_F_PACKED flag is set):
/// - `desc_table_addr`: Packed descriptor ring address
/// - `used_ring_addr`: Driver event suppression structure address
/// - `avail_ring_addr`: Device event suppression structure address
#[derive(Default, Clone, Copy)]
pub struct VringConfigData {
/// Maximum queue size supported by the driver.
Expand All @@ -33,11 +43,11 @@ pub struct VringConfigData {
pub queue_size: u16,
/// Bitmask of vring flags.
pub flags: u32,
/// Descriptor table address.
/// Descriptor table address (split) / Packed descriptor ring address (packed).
pub desc_table_addr: u64,
/// Used ring buffer address.
/// Used ring buffer address (split) / Driver event suppression address (packed).
pub used_ring_addr: u64,
/// Available ring buffer address.
/// Available ring buffer address (split) / Device event suppression address (packed).
pub avail_ring_addr: u64,
/// Optional address for logging.
pub log_addr: Option<u64>,
Expand Down
81 changes: 61 additions & 20 deletions vhost/src/vhost_kern/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ use super::{
VringConfigData, VHOST_MAX_MEMORY_REGIONS,
};

#[cfg(feature = "vhost-user")]
use super::vhost_user::message::VhostUserVringAddrFlags;

pub mod vhost_binding;
use self::vhost_binding::*;

Expand Down Expand Up @@ -73,26 +76,64 @@ pub trait VhostKernBackend: AsRawFd {
}

let m = self.mem().memory();
let desc_table_size = 16 * u64::from(queue_size) as GuestUsize;
let avail_ring_size = 6 + 2 * u64::from(queue_size) as GuestUsize;
let used_ring_size = 6 + 8 * u64::from(queue_size) as GuestUsize;
if GuestAddress(config_data.desc_table_addr)
.checked_add(desc_table_size)
.is_none_or(|v| !m.address_in_range(v))
{
return false;
}
if GuestAddress(config_data.avail_ring_addr)
.checked_add(avail_ring_size)
.is_none_or(|v| !m.address_in_range(v))
{
return false;
}
if GuestAddress(config_data.used_ring_addr)
.checked_add(used_ring_size)
.is_none_or(|v| !m.address_in_range(v))
{
return false;

// Check if packed ring format is being used
#[cfg(feature = "vhost-user")]
let is_packed =
config_data.flags & VhostUserVringAddrFlags::VHOST_VRING_F_PACKED.bits() != 0;
#[cfg(not(feature = "vhost-user"))]
let is_packed = false;

if is_packed {
// Packed ring: single descriptor ring layout
let desc_ring_size = 16 * u64::from(queue_size) as GuestUsize;
let driver_event_size = 4; // 4 bytes for driver event suppression
let device_event_size = 4; // 4 bytes for device event suppression

// Validate packed descriptor ring
if GuestAddress(config_data.desc_table_addr)
.checked_add(desc_ring_size)
.is_none_or(|v| !m.address_in_range(v))
{
return false;
}
// Validate driver event suppression area (available)
if GuestAddress(config_data.avail_ring_addr)
.checked_add(driver_event_size)
.is_none_or(|v| !m.address_in_range(v))
{
return false;
}
// Validate device event suppression area (used)
if GuestAddress(config_data.used_ring_addr)
.checked_add(device_event_size)
.is_none_or(|v| !m.address_in_range(v))
{
return false;
}
} else {
// Split ring validation (existing logic)
let desc_table_size = 16 * u64::from(queue_size) as GuestUsize;
let avail_ring_size = 6 + 2 * u64::from(queue_size) as GuestUsize;
let used_ring_size = 6 + 8 * u64::from(queue_size) as GuestUsize;
if GuestAddress(config_data.desc_table_addr)
.checked_add(desc_table_size)
.is_none_or(|v| !m.address_in_range(v))
{
return false;
}
if GuestAddress(config_data.avail_ring_addr)
.checked_add(avail_ring_size)
.is_none_or(|v| !m.address_in_range(v))
{
return false;
}
if GuestAddress(config_data.used_ring_addr)
.checked_add(used_ring_size)
.is_none_or(|v| !m.address_in_range(v))
{
return false;
}
}

config_data.is_log_addr_valid()
Expand Down
86 changes: 80 additions & 6 deletions vhost/src/vhost_user/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ bitflags! {
const LOG_ALL = 0x400_0000;
/// Feature flag for the protocol feature.
const PROTOCOL_FEATURES = 0x4000_0000;
/// Feature flag for packed virtqueue support (VIRTIO_F_RING_PACKED, bit 34).
const RING_PACKED = 0x4_0000_0000;
}
}

Expand Down Expand Up @@ -713,6 +715,9 @@ bitflags! {
/// Support log of vring operations.
/// Modifications to "used" vring should be logged.
const VHOST_VRING_F_LOG = 0x1;
/// Indicates packed virtqueue format.
/// When set, the vring uses packed layout instead of split layout.
const VHOST_VRING_F_PACKED = 0x2;
}
}

Expand Down Expand Up @@ -777,12 +782,28 @@ impl VhostUserMsgValidator for VhostUserVringAddr {
fn is_valid(&self) -> bool {
if (self.flags & !VhostUserVringAddrFlags::all().bits()) != 0 {
return false;
} else if self.descriptor & 0xf != 0 {
return false;
} else if self.available & 0x1 != 0 {
return false;
} else if self.used & 0x3 != 0 {
return false;
}

// Check if packed ring format
if self.flags & VhostUserVringAddrFlags::VHOST_VRING_F_PACKED.bits() != 0 {
// Packed ring validation:
// - descriptor ring must be 16-byte aligned
if self.descriptor & 0xf != 0 {
return false;
}
// - driver/device event suppression areas must be 4-byte aligned
if self.available & 0x3 != 0 || self.used & 0x3 != 0 {
return false;
}
} else {
// Split ring validation (current logic)
if self.descriptor & 0xf != 0 {
return false;
} else if self.available & 0x1 != 0 {
return false;
} else if self.used & 0x3 != 0 {
return false;
}
}
true
}
Expand Down Expand Up @@ -1417,4 +1438,57 @@ mod tests {
msg.flags |= 0x4;
assert!(!msg.is_valid());
}

#[test]
fn test_packed_virtqueue_features() {
let a = VhostUserVirtioFeatures::RING_PACKED.bits();
assert_eq!(a, 0x4_0000_0000);

let a = VhostUserVringAddrFlags::VHOST_VRING_F_PACKED.bits();
assert_eq!(a, 0x2);

let combined = VhostUserVringAddrFlags::VHOST_VRING_F_LOG
| VhostUserVringAddrFlags::VHOST_VRING_F_PACKED;
let a = combined.bits();
assert_eq!(a, 0x3);
}

#[test]
fn test_packed_vring_addr_validation() {
let mut addr = VhostUserVringAddr::new(
0,
VhostUserVringAddrFlags::VHOST_VRING_F_PACKED,
0x1000,
0x2000,
0x3000,
0x4000,
);

let a = addr.index;
assert_eq!(a, 0);
let a = addr.flags;
assert_eq!(a, VhostUserVringAddrFlags::VHOST_VRING_F_PACKED.bits());
let a = addr.descriptor;
assert_eq!(a, 0x1000);
let a = addr.used;
assert_eq!(a, 0x2000);
let a = addr.available;
assert_eq!(a, 0x3000);
let a = addr.log;
assert_eq!(a, 0x4000);
assert!(addr.is_valid());

addr.descriptor = 0x1001;
assert!(!addr.is_valid());
addr.descriptor = 0x1000;

addr.available = 0x3001;
assert!(!addr.is_valid());
addr.available = 0x3000;

addr.used = 0x2001;
assert!(!addr.is_valid());
addr.used = 0x2000;
assert!(addr.is_valid());
}
}