diff --git a/vhost/CHANGELOG.md b/vhost/CHANGELOG.md index d4e62dd6..0640fa59 100644 --- a/vhost/CHANGELOG.md +++ b/vhost/CHANGELOG.md @@ -3,6 +3,7 @@ ## [Unreleased] ### Added +- Add support for packed virtqueues in vhost-user and vhost-kern backends ### Changed ### Deprecated ### Fixed diff --git a/vhost/src/backend.rs b/vhost/src/backend.rs index bd4a83b2..34f27da4 100644 --- a/vhost/src/backend.rs +++ b/vhost/src/backend.rs @@ -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. @@ -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, diff --git a/vhost/src/vhost_kern/mod.rs b/vhost/src/vhost_kern/mod.rs index 97f5819c..d8e500aa 100644 --- a/vhost/src/vhost_kern/mod.rs +++ b/vhost/src/vhost_kern/mod.rs @@ -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::*; @@ -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() diff --git a/vhost/src/vhost_user/message.rs b/vhost/src/vhost_user/message.rs index c66bd446..d23b3dec 100644 --- a/vhost/src/vhost_user/message.rs +++ b/vhost/src/vhost_user/message.rs @@ -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; } } @@ -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; } } @@ -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 } @@ -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()); + } }