mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
vmm: Accept an externally-opened iommufd FD
The CLI `--platform` option now accepts `iommufd_fd=<n>` alongside the existing `iommufd=on|off`. Signed-off-by: Bo Chen <bchen@crusoe.ai> Assisted-by: Claude:Opus-4.7
This commit is contained in:
+70
-14
@@ -349,9 +349,13 @@ pub enum ValidationError {
|
|||||||
/// A DeviceConfig specified both `path` and `fd`.
|
/// A DeviceConfig specified both `path` and `fd`.
|
||||||
#[error("VFIO device config must specify at most one of `path=` or `fd=`")]
|
#[error("VFIO device config must specify at most one of `path=` or `fd=`")]
|
||||||
VfioDeviceBothPathAndFd,
|
VfioDeviceBothPathAndFd,
|
||||||
/// FD-based VFIO requires the `iommufd` platform option to be enabled.
|
/// FD-based VFIO requires an externally-supplied iommufd FD so the
|
||||||
#[error("VFIO device `fd=` requires platform `iommufd=on`")]
|
/// cdev's bind state can survive across an in-process VM reboot.
|
||||||
VfioFdRequiresIommufd,
|
#[error("VFIO device `fd=` requires platform `iommufd_fd=<fd>`")]
|
||||||
|
VfioFdRequiresIommufdFd,
|
||||||
|
/// `iommufd_fd` was provided without also enabling the iommufd backend.
|
||||||
|
#[error("Platform `iommufd_fd=<fd>` requires `iommufd=on`")]
|
||||||
|
IommufdFdRequiresIommufd,
|
||||||
/// Provided MTU is lower than what the VIRTIO specification expects
|
/// Provided MTU is lower than what the VIRTIO specification expects
|
||||||
#[error("Provided MTU {0} is lower than 1280 (expected by VIRTIO specification)")]
|
#[error("Provided MTU {0} is lower than 1280 (expected by VIRTIO specification)")]
|
||||||
InvalidMtu(u16),
|
InvalidMtu(u16),
|
||||||
@@ -853,7 +857,8 @@ impl PlatformConfig {
|
|||||||
static SYNTAX: LazyLock<String> = LazyLock::new(|| {
|
static SYNTAX: LazyLock<String> = LazyLock::new(|| {
|
||||||
let mut syntax = "Platform configuration parameters \
|
let mut syntax = "Platform configuration parameters \
|
||||||
\"num_pci_segments=<num_pci_segments>,iommu_segments=<list_of_segments>,\
|
\"num_pci_segments=<num_pci_segments>,iommu_segments=<list_of_segments>,\
|
||||||
iommu_address_width=<bits>,iommufd=on|off,vfio_p2p_dma=on|off,system_manufacturer=<dmi_system_manufacturer>,\
|
iommu_address_width=<bits>,iommufd=on|off,iommufd_fd=<fd>,vfio_p2p_dma=on|off,\
|
||||||
|
system_manufacturer=<dmi_system_manufacturer>,\
|
||||||
system_product_name=<dmi_system_product_name>,system_version=<dmi_system_version>,\
|
system_product_name=<dmi_system_product_name>,system_version=<dmi_system_version>,\
|
||||||
system_serial_number=<dmi_system_serial_number>,system_uuid=<dmi_system_uuid>,\
|
system_serial_number=<dmi_system_serial_number>,system_uuid=<dmi_system_uuid>,\
|
||||||
system_sku_number=<dmi_system_sku_number>,system_family=<dmi_system_family>,\
|
system_sku_number=<dmi_system_sku_number>,system_family=<dmi_system_family>,\
|
||||||
@@ -926,6 +931,7 @@ impl PlatformConfig {
|
|||||||
.add("uuid")
|
.add("uuid")
|
||||||
.add("oem_strings")
|
.add("oem_strings")
|
||||||
.add("iommufd")
|
.add("iommufd")
|
||||||
|
.add("iommufd_fd")
|
||||||
.add("vfio_p2p_dma");
|
.add("vfio_p2p_dma");
|
||||||
for field in SMBIOS_STRING_FIELDS {
|
for field in SMBIOS_STRING_FIELDS {
|
||||||
parser.add(field.key);
|
parser.add(field.key);
|
||||||
@@ -952,11 +958,14 @@ impl PlatformConfig {
|
|||||||
.convert::<StringList>("oem_strings")
|
.convert::<StringList>("oem_strings")
|
||||||
.map_err(Error::ParsePlatform)?
|
.map_err(Error::ParsePlatform)?
|
||||||
.map(|v| v.0.into_boxed_slice());
|
.map(|v| v.0.into_boxed_slice());
|
||||||
|
let iommufd_fd = parser
|
||||||
|
.convert::<i32>("iommufd_fd")
|
||||||
|
.map_err(Error::ParsePlatform)?;
|
||||||
|
// `iommufd_fd=<n>` implies `iommufd=on` unless the user explicitly set the value.
|
||||||
let iommufd = parser
|
let iommufd = parser
|
||||||
.convert::<Toggle>("iommufd")
|
.convert::<Toggle>("iommufd")
|
||||||
.map_err(Error::ParsePlatform)?
|
.map_err(Error::ParsePlatform)?
|
||||||
.unwrap_or(Toggle(false))
|
.map_or(iommufd_fd.is_some(), |Toggle(v)| v);
|
||||||
.0;
|
|
||||||
let vfio_p2p_dma = parser
|
let vfio_p2p_dma = parser
|
||||||
.convert::<Toggle>("vfio_p2p_dma")
|
.convert::<Toggle>("vfio_p2p_dma")
|
||||||
.map_err(Error::ParsePlatform)?
|
.map_err(Error::ParsePlatform)?
|
||||||
@@ -989,6 +998,7 @@ impl PlatformConfig {
|
|||||||
system_sku_number: None,
|
system_sku_number: None,
|
||||||
chassis_asset_tag: None,
|
chassis_asset_tag: None,
|
||||||
iommufd,
|
iommufd,
|
||||||
|
iommufd_fd,
|
||||||
#[cfg(feature = "tdx")]
|
#[cfg(feature = "tdx")]
|
||||||
tdx,
|
tdx,
|
||||||
#[cfg(feature = "sev_snp")]
|
#[cfg(feature = "sev_snp")]
|
||||||
@@ -1047,6 +1057,10 @@ impl PlatformConfig {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.iommufd_fd.is_some() && !self.iommufd {
|
||||||
|
return Err(ValidationError::IommufdFdRequiresIommufd);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2461,9 +2475,12 @@ impl DeviceConfig {
|
|||||||
(None, None) => return Err(ValidationError::VfioDeviceNeitherPathNorFd),
|
(None, None) => return Err(ValidationError::VfioDeviceNeitherPathNorFd),
|
||||||
(Some(_), Some(_)) => return Err(ValidationError::VfioDeviceBothPathAndFd),
|
(Some(_), Some(_)) => return Err(ValidationError::VfioDeviceBothPathAndFd),
|
||||||
(None, Some(_)) => {
|
(None, Some(_)) => {
|
||||||
let iommufd_on = vm_config.platform.as_ref().is_some_and(|p| p.iommufd);
|
let iommufd_fd_set = vm_config
|
||||||
if !iommufd_on {
|
.platform
|
||||||
return Err(ValidationError::VfioFdRequiresIommufd);
|
.as_ref()
|
||||||
|
.is_some_and(|p| p.iommufd_fd.is_some());
|
||||||
|
if !iommufd_fd_set {
|
||||||
|
return Err(ValidationError::VfioFdRequiresIommufdFd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Some(_), None) => {}
|
(Some(_), None) => {}
|
||||||
@@ -4872,6 +4889,26 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_platform_iommufd_fd_parsing() -> Result<()> {
|
||||||
|
// `iommufd_fd=N` alone implies `iommufd=on`.
|
||||||
|
let p = PlatformConfig::parse("iommufd_fd=42")?;
|
||||||
|
assert!(p.iommufd);
|
||||||
|
assert_eq!(p.iommufd_fd, Some(42));
|
||||||
|
|
||||||
|
// Explicit `iommufd=on,iommufd_fd=N` is the same.
|
||||||
|
let p = PlatformConfig::parse("iommufd=on,iommufd_fd=42")?;
|
||||||
|
assert!(p.iommufd);
|
||||||
|
assert_eq!(p.iommufd_fd, Some(42));
|
||||||
|
|
||||||
|
// No flags → both default to off.
|
||||||
|
let p = PlatformConfig::parse("")?;
|
||||||
|
assert!(!p.iommufd);
|
||||||
|
assert_eq!(p.iommufd_fd, None);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vsock_parsing() -> Result<()> {
|
fn test_vsock_parsing() -> Result<()> {
|
||||||
// socket and cid is required
|
// socket and cid is required
|
||||||
@@ -5276,6 +5313,7 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
|
|||||||
system_uuid: None,
|
system_uuid: None,
|
||||||
oem_strings: None,
|
oem_strings: None,
|
||||||
iommufd: false,
|
iommufd: false,
|
||||||
|
iommufd_fd: None,
|
||||||
vfio_p2p_dma: default_platformconfig_vfio_p2p_dma(),
|
vfio_p2p_dma: default_platformconfig_vfio_p2p_dma(),
|
||||||
system_manufacturer: None,
|
system_manufacturer: None,
|
||||||
system_product_name: None,
|
system_product_name: None,
|
||||||
@@ -6145,10 +6183,12 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
|
|||||||
]);
|
]);
|
||||||
invalid_config.validate().unwrap_err();
|
invalid_config.validate().unwrap_err();
|
||||||
|
|
||||||
// An fd-backed DeviceConfig is only valid with iommufd enabled.
|
// An fd-backed DeviceConfig is only valid with
|
||||||
|
// externally-supplied fd is provided for iommufd too
|
||||||
let mut fd_valid_config = valid_config.clone();
|
let mut fd_valid_config = valid_config.clone();
|
||||||
fd_valid_config.platform = Some(PlatformConfig {
|
fd_valid_config.platform = Some(PlatformConfig {
|
||||||
iommufd: true,
|
iommufd: true,
|
||||||
|
iommufd_fd: Some(8),
|
||||||
..platform_fixture()
|
..platform_fixture()
|
||||||
});
|
});
|
||||||
fd_valid_config.devices = Some(vec![DeviceConfig {
|
fd_valid_config.devices = Some(vec![DeviceConfig {
|
||||||
@@ -6158,11 +6198,27 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
|
|||||||
}]);
|
}]);
|
||||||
fd_valid_config.validate().unwrap();
|
fd_valid_config.validate().unwrap();
|
||||||
|
|
||||||
let mut fd_without_iommufd = fd_valid_config.clone();
|
let mut fd_without_iommufd_fd = fd_valid_config.clone();
|
||||||
fd_without_iommufd.platform = None;
|
fd_without_iommufd_fd.platform = Some(PlatformConfig {
|
||||||
|
iommufd: true,
|
||||||
|
iommufd_fd: None,
|
||||||
|
..platform_fixture()
|
||||||
|
});
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
fd_without_iommufd.validate(),
|
fd_without_iommufd_fd.validate(),
|
||||||
Err(ValidationError::VfioFdRequiresIommufd),
|
Err(ValidationError::VfioFdRequiresIommufdFd),
|
||||||
|
));
|
||||||
|
|
||||||
|
// iommufd_fd without iommufd=on is rejected at PlatformConfig::validate.
|
||||||
|
let mut iommufd_fd_without_iommufd = valid_config.clone();
|
||||||
|
iommufd_fd_without_iommufd.platform = Some(PlatformConfig {
|
||||||
|
iommufd: false,
|
||||||
|
iommufd_fd: Some(8),
|
||||||
|
..platform_fixture()
|
||||||
|
});
|
||||||
|
assert!(matches!(
|
||||||
|
iommufd_fd_without_iommufd.validate(),
|
||||||
|
Err(ValidationError::IommufdFdRequiresIommufd),
|
||||||
));
|
));
|
||||||
|
|
||||||
// Exactly one of path and fd must be set.
|
// Exactly one of path and fd must be set.
|
||||||
|
|||||||
@@ -159,6 +159,9 @@ pub struct PlatformConfig {
|
|||||||
pub sev_snp: bool,
|
pub sev_snp: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub iommufd: bool,
|
pub iommufd: bool,
|
||||||
|
// FDs are not serialized and any deserialized value is invalid; see NetConfig::fds.
|
||||||
|
#[serde(default, deserialize_with = "deserialize_platformconfig_iommufd_fd")]
|
||||||
|
pub iommufd_fd: Option<i32>,
|
||||||
#[serde(default = "default_platformconfig_vfio_p2p_dma")]
|
#[serde(default = "default_platformconfig_vfio_p2p_dma")]
|
||||||
pub vfio_p2p_dma: bool,
|
pub vfio_p2p_dma: bool,
|
||||||
}
|
}
|
||||||
@@ -207,6 +210,21 @@ impl PlatformConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deserialize_platformconfig_iommufd_fd<'de, D>(d: D) -> Result<Option<i32>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let invalid_fd: Option<i32> = Option::deserialize(d)?;
|
||||||
|
if invalid_fd.is_some() {
|
||||||
|
debug!(
|
||||||
|
"FD in 'PlatformConfig::iommufd_fd' won't be deserialized as it is most likely invalid now. Deserializing it as -1."
|
||||||
|
);
|
||||||
|
Ok(Some(-1))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT: u32 = 1;
|
pub const DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT: u32 = 1;
|
||||||
|
|
||||||
fn default_pci_segment_aperture_weight() -> u32 {
|
fn default_pci_segment_aperture_weight() -> u32 {
|
||||||
|
|||||||
Reference in New Issue
Block a user