vmm: make PCI BDF configurable for balloon

Add shared PCI config to virtio-balloon.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This commit is contained in:
Philipp Schuster
2026-05-19 14:58:52 +02:00
committed by Rob Bradford
parent 08bd7727ff
commit 5aa0587f2a
5 changed files with 126 additions and 8 deletions
+12
View File
@@ -6092,6 +6092,7 @@ mod common_parallel {
.args(["--kernel", kernel_path.to_str().unwrap()]) .args(["--kernel", kernel_path.to_str().unwrap()])
.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
.args(["--console", "tty,pci_device_id=7"]) .args(["--console", "tty,pci_device_id=7"])
.args(["--balloon", "size=0,pci_device_id=8"])
.default_net() .default_net()
.default_disks() .default_disks()
.capture_output(); .capture_output();
@@ -6113,6 +6114,17 @@ mod common_parallel {
.is_ok() .is_ok()
})); }));
// Make sure an explicit BDF for virtio-balloon is set.
assert!(wait_until(Duration::from_secs(10), || {
ssh_command_ip_with_auth(
"lspci -n | grep \"00:08.0\"",
&default_guest_auth(),
&guest.network.guest_ip0,
Some(Duration::from_secs(1)),
)
.is_ok()
}));
let (cmd_success, cmd_stdout, _) = remote_command_w_output( let (cmd_success, cmd_stdout, _) = remote_command_w_output(
&api_socket, &api_socket,
"add-net", "add-net",
+11
View File
@@ -1127,6 +1127,17 @@ components:
- size - size
type: object type: object
properties: properties:
id:
type: string
pci_segment:
type: integer
format: int16
pci_device_id:
type: integer
format: uint8
iommu:
type: boolean
default: false
size: size:
type: integer type: integer
format: int64 format: int64
+89 -1
View File
@@ -1896,13 +1896,15 @@ impl RtcConfig {
impl BalloonConfig { impl BalloonConfig {
pub const SYNTAX: &'static str = "Balloon parameters \"size=<balloon_size>,deflate_on_oom=on|off,\ pub const SYNTAX: &'static str = "Balloon parameters \"size=<balloon_size>,deflate_on_oom=on|off,\
free_page_reporting=on|off\""; free_page_reporting=on|off,iommu=on|off,id=<device_id>,pci_segment=<segment_id>,\
pci_device_id=<pci_slot>\"";
pub fn parse(balloon: &str) -> Result<Self> { pub fn parse(balloon: &str) -> Result<Self> {
let mut parser = OptionParser::new(); let mut parser = OptionParser::new();
parser.add("size"); parser.add("size");
parser.add("deflate_on_oom"); parser.add("deflate_on_oom");
parser.add("free_page_reporting"); parser.add("free_page_reporting");
parser.add_all(PciDeviceCommonConfig::OPTIONS_IOMMU);
parser.parse(balloon).map_err(Error::ParseBalloon)?; parser.parse(balloon).map_err(Error::ParseBalloon)?;
let size = parser let size = parser
@@ -1922,12 +1924,19 @@ impl BalloonConfig {
.unwrap_or(Toggle(false)) .unwrap_or(Toggle(false))
.0; .0;
let pci_common = PciDeviceCommonConfig::parse(balloon)?;
Ok(BalloonConfig { Ok(BalloonConfig {
pci_common,
size, size,
deflate_on_oom, deflate_on_oom,
free_page_reporting, free_page_reporting,
}) })
} }
pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> {
self.pci_common.validate(vm_config)
}
} }
impl GenericVhostUserConfig { impl GenericVhostUserConfig {
@@ -3258,6 +3267,10 @@ impl VmConfig {
} }
if let Some(balloon) = &self.balloon { if let Some(balloon) = &self.balloon {
balloon.validate(self)?;
Self::validate_identifier(&mut id_list, &balloon.pci_common.id)?;
self.iommu |= balloon.pci_common.iommu;
let ram_size = self.memory.total_size(); let ram_size = self.memory.total_size();
if balloon.size >= ram_size { if balloon.size >= ram_size {
return Err(ValidationError::BalloonLargerThanRam( return Err(ValidationError::BalloonLargerThanRam(
@@ -4373,6 +4386,40 @@ mod unit_tests {
Ok(()) Ok(())
} }
#[test]
fn test_parse_balloon() -> Result<()> {
assert_eq!(
BalloonConfig::parse(
"size=128M,deflate_on_oom=on,free_page_reporting=on,pci_segment=1,pci_device_id=7"
)?,
BalloonConfig {
pci_common: PciDeviceCommonConfig {
pci_segment: 1,
pci_device_id: Some(7),
..Default::default()
},
size: 128 << 20,
deflate_on_oom: true,
free_page_reporting: true,
}
);
assert_eq!(
BalloonConfig::parse("size=0,iommu=on")?,
BalloonConfig {
pci_common: PciDeviceCommonConfig {
iommu: true,
..Default::default()
},
size: 0,
deflate_on_oom: false,
free_page_reporting: false,
}
);
Ok(())
}
fn fs_fixture() -> FsConfig { fn fs_fixture() -> FsConfig {
FsConfig { FsConfig {
pci_common: PciDeviceCommonConfig::default(), pci_common: PciDeviceCommonConfig::default(),
@@ -6124,6 +6171,47 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}"
Err(ValidationError::InvalidPciDeviceId(pci::NUM_DEVICE_IDS + 1)) Err(ValidationError::InvalidPciDeviceId(pci::NUM_DEVICE_IDS + 1))
); );
// Invalid balloon BDF - Same ID as Root device
let mut invalid_config = valid_config.clone();
invalid_config.balloon = Some(BalloonConfig {
pci_common: PciDeviceCommonConfig {
pci_device_id: Some(pci::PCI_ROOT_DEVICE_ID),
..Default::default()
},
size: 0,
deflate_on_oom: false,
free_page_reporting: false,
});
assert_eq!(
invalid_config.validate(),
Err(ValidationError::ReservedPciDeviceId(
pci::PCI_ROOT_DEVICE_ID
))
);
// Invalid balloon ID - Duplicate identifier
let mut invalid_config = valid_config.clone();
invalid_config.balloon = Some(BalloonConfig {
pci_common: PciDeviceCommonConfig {
id: Some("test0".to_string()),
..Default::default()
},
size: 0,
deflate_on_oom: false,
free_page_reporting: false,
});
invalid_config.disks = Some(vec![DiskConfig {
pci_common: PciDeviceCommonConfig {
id: Some("test0".to_string()),
..Default::default()
},
..disk_fixture()
}]);
assert_eq!(
invalid_config.validate(),
Err(ValidationError::IdentifierNotUnique("test0".to_string()))
);
// Invalid console BDF - Same ID as Root device // Invalid console BDF - Same ID as Root device
let mut invalid_config = valid_config.clone(); let mut invalid_config = valid_config.clone();
invalid_config.console.pci_common.pci_device_id = Some(pci::PCI_ROOT_DEVICE_ID); invalid_config.console.pci_common.pci_device_id = Some(pci::PCI_ROOT_DEVICE_ID);
+12 -7
View File
@@ -3664,8 +3664,16 @@ impl DeviceManager {
&mut self, &mut self,
snapshot: Option<&Snapshot>, snapshot: Option<&Snapshot>,
) -> DeviceManagerResult<()> { ) -> DeviceManagerResult<()> {
if let Some(balloon_config) = &self.config.lock().unwrap().balloon { let mut balloon_config = self.config.lock().unwrap().balloon.clone();
let id = String::from(BALLOON_DEVICE_NAME); if let Some(balloon_config) = &mut balloon_config {
let id = match balloon_config.pci_common.id.as_ref() {
Some(id) => id.clone(),
None => balloon_config
.pci_common
.id
.insert(BALLOON_DEVICE_NAME.to_string())
.clone(),
};
info!("Creating virtio-balloon device: id = {id}"); info!("Creating virtio-balloon device: id = {id}");
let virtio_balloon_device = Arc::new(Mutex::new( let virtio_balloon_device = Arc::new(Mutex::new(
@@ -3674,7 +3682,7 @@ impl DeviceManager {
balloon_config.size, balloon_config.size,
balloon_config.deflate_on_oom, balloon_config.deflate_on_oom,
balloon_config.free_page_reporting, balloon_config.free_page_reporting,
self.force_access_platform, self.force_access_platform | balloon_config.pci_common.iommu,
self.seccomp_action.clone(), self.seccomp_action.clone(),
self.exit_evt self.exit_evt
.try_clone() .try_clone()
@@ -3690,10 +3698,7 @@ impl DeviceManager {
self.virtio_devices.push(MetaVirtioDevice { self.virtio_devices.push(MetaVirtioDevice {
virtio_device: Arc::clone(&virtio_balloon_device) virtio_device: Arc::clone(&virtio_balloon_device)
as Arc<Mutex<dyn virtio_devices::VirtioDevice>>, as Arc<Mutex<dyn virtio_devices::VirtioDevice>>,
pci_common: PciDeviceCommonConfig { pci_common: balloon_config.pci_common.clone(),
id: Some(id.clone()),
..Default::default()
},
dma_handler: None, dma_handler: None,
}); });
+2
View File
@@ -535,6 +535,8 @@ impl ApplyLandlock for RngConfig {
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct BalloonConfig { pub struct BalloonConfig {
#[serde(flatten)]
pub pci_common: PciDeviceCommonConfig,
pub size: u64, pub size: u64,
/// Option to deflate the balloon in case the guest is out of memory. /// Option to deflate the balloon in case the guest is out of memory.
#[serde(default)] #[serde(default)]