devices: Add pvmemcontrol device

Pvmemcontrol provides a way for the guest to control its physical memory
properties, and enables optimizations and security features. For
example, the guest can provide information to the host where parts of a
hugepage may be unbacked, or sensitive data may not be swapped out, etc.

Pvmemcontrol allows guests to manipulate its gPTE entries in the SLAT,
and also some other properties of the memory map the back's host memory.
This is achieved by using the KVM_CAP_SYNC_MMU capability. When this
capability is available, the changes in the backing of the memory region
on the host are automatically reflected into the guest. For example, an
mmap() or madvise() that affects the region will be made visible
immediately.

There are two components of the implementation: the guest Linux driver
and Virtual Machine Monitor (VMM) device. A guest-allocated shared
buffer is negotiated per-cpu through a few PCI MMIO registers, the VMM
device assigns a unique command for each per-cpu buffer. The guest
writes its pvmemcontrol request in the per-cpu buffer, then writes the
corresponding command into the command register, calling into the VMM
device to perform the pvmemcontrol request.

The synchronous per-cpu shared buffer approach avoids the kick and busy
waiting that the guest would have to do with virtio virtqueue transport.

The Cloud Hypervisor component can be enabled with --pvmemcontrol.

Co-developed-by: Stanko Novakovic <stanko@google.com>
Co-developed-by: Pasha Tatashin <tatashin@google.com>
Signed-off-by: Yuanchu Xie <yuanchu@google.com>
This commit is contained in:
Yuanchu Xie
2023-10-25 22:46:47 +00:00
committed by Liu Wei
parent 4bf2d4f7dd
commit 5f18ac3bc0
14 changed files with 1029 additions and 1 deletions

View File

@@ -482,6 +482,8 @@ pub struct VmParams<'a> {
pub user_devices: Option<Vec<&'a str>>,
pub vdpa: Option<Vec<&'a str>>,
pub vsock: Option<&'a str>,
#[cfg(feature = "pvmemcontrol")]
pub pvmemcontrol: bool,
pub pvpanic: bool,
#[cfg(target_arch = "x86_64")]
pub sgx_epc: Option<Vec<&'a str>>,
@@ -543,6 +545,8 @@ impl<'a> VmParams<'a> {
.get_many::<String>("vdpa")
.map(|x| x.map(|y| y as &str).collect());
let vsock: Option<&str> = args.get_one::<String>("vsock").map(|x| x as &str);
#[cfg(feature = "pvmemcontrol")]
let pvmemcontrol = args.get_flag("pvmemcontrol");
let pvpanic = args.get_flag("pvpanic");
#[cfg(target_arch = "x86_64")]
let sgx_epc: Option<Vec<&str>> = args
@@ -591,6 +595,8 @@ impl<'a> VmParams<'a> {
user_devices,
vdpa,
vsock,
#[cfg(feature = "pvmemcontrol")]
pvmemcontrol,
pvpanic,
#[cfg(target_arch = "x86_64")]
sgx_epc,
@@ -2772,6 +2778,11 @@ impl VmConfig {
balloon = Some(BalloonConfig::parse(balloon_params)?);
}
#[cfg(feature = "pvmemcontrol")]
let pvmemcontrol: Option<PvmemcontrolConfig> = vm_params
.pvmemcontrol
.then_some(PvmemcontrolConfig::default());
let mut fs: Option<Vec<FsConfig>> = None;
if let Some(fs_list) = &vm_params.fs {
let mut fs_config_list = Vec::new();
@@ -2930,6 +2941,8 @@ impl VmConfig {
user_devices,
vdpa,
vsock,
#[cfg(feature = "pvmemcontrol")]
pvmemcontrol,
pvpanic: vm_params.pvpanic,
iommu: false, // updated in VmConfig::validate()
#[cfg(target_arch = "x86_64")]
@@ -3049,6 +3062,8 @@ impl Clone for VmConfig {
net: self.net.clone(),
rng: self.rng.clone(),
balloon: self.balloon.clone(),
#[cfg(feature = "pvmemcontrol")]
pvmemcontrol: self.pvmemcontrol.clone(),
fs: self.fs.clone(),
pmem: self.pmem.clone(),
serial: self.serial.clone(),
@@ -3838,6 +3853,8 @@ mod tests {
user_devices: None,
vdpa: None,
vsock: None,
#[cfg(feature = "pvmemcontrol")]
pvmemcontrol: None,
pvpanic: false,
iommu: false,
#[cfg(target_arch = "x86_64")]
@@ -4047,6 +4064,8 @@ mod tests {
user_devices: None,
vdpa: None,
vsock: None,
#[cfg(feature = "pvmemcontrol")]
pvmemcontrol: None,
pvpanic: false,
iommu: false,
#[cfg(target_arch = "x86_64")]

View File

@@ -49,6 +49,8 @@ use devices::gic;
use devices::ioapic;
#[cfg(target_arch = "aarch64")]
use devices::legacy::Pl011;
#[cfg(feature = "pvmemcontrol")]
use devices::pvmemcontrol::{PvmemcontrolBusDevice, PvmemcontrolPciDevice};
use devices::{
interrupt_controller, interrupt_controller::InterruptController, AcpiNotificationFlags,
};
@@ -118,6 +120,8 @@ const DEBUGCON_DEVICE_NAME: &str = "__debug_console";
const GPIO_DEVICE_NAME: &str = "__gpio";
const RNG_DEVICE_NAME: &str = "__rng";
const IOMMU_DEVICE_NAME: &str = "__iommu";
#[cfg(feature = "pvmemcontrol")]
const PVMEMCONTROL_DEVICE_NAME: &str = "__pvmemcontrol";
const BALLOON_DEVICE_NAME: &str = "__balloon";
const CONSOLE_DEVICE_NAME: &str = "__console";
const PVPANIC_DEVICE_NAME: &str = "__pvpanic";
@@ -195,6 +199,10 @@ pub enum DeviceManagerError {
/// Cannot create virtio-balloon device
CreateVirtioBalloon(io::Error),
/// Cannot create pvmemcontrol device
#[cfg(feature = "pvmemcontrol")]
CreatePvmemcontrol(io::Error),
/// Cannot create virtio-watchdog device
CreateVirtioWatchdog(io::Error),
@@ -886,6 +894,12 @@ pub struct DeviceManager {
// GPIO device for AArch64
gpio_device: Option<Arc<Mutex<devices::legacy::Gpio>>>,
#[cfg(feature = "pvmemcontrol")]
pvmemcontrol_devices: Option<(
Arc<PvmemcontrolBusDevice>,
Arc<Mutex<PvmemcontrolPciDevice>>,
)>,
// pvpanic device
pvpanic_device: Option<Arc<Mutex<devices::PvPanicDevice>>>,
@@ -1165,6 +1179,8 @@ impl DeviceManager {
virtio_mem_devices: Vec::new(),
#[cfg(target_arch = "aarch64")]
gpio_device: None,
#[cfg(feature = "pvmemcontrol")]
pvmemcontrol_devices: None,
pvpanic_device: None,
force_iommu,
io_uring_supported: None,
@@ -1278,6 +1294,17 @@ impl DeviceManager {
self.virtio_devices = virtio_devices;
// Add pvmemcontrol if required
#[cfg(feature = "pvmemcontrol")]
{
if self.config.lock().unwrap().pvmemcontrol.is_some() {
let (pvmemcontrol_bus_device, pvmemcontrol_pci_device) =
self.make_pvmemcontrol_device()?;
self.pvmemcontrol_devices =
Some((pvmemcontrol_bus_device, pvmemcontrol_pci_device));
}
}
if self.config.clone().lock().unwrap().pvpanic {
self.pvpanic_device = self.add_pvpanic_device()?;
}
@@ -3048,6 +3075,48 @@ impl DeviceManager {
Ok(devices)
}
#[cfg(feature = "pvmemcontrol")]
fn make_pvmemcontrol_device(
&mut self,
) -> DeviceManagerResult<(
Arc<PvmemcontrolBusDevice>,
Arc<Mutex<PvmemcontrolPciDevice>>,
)> {
let id = String::from(PVMEMCONTROL_DEVICE_NAME);
let pci_segment_id = 0x0_u16;
let (pci_segment_id, pci_device_bdf, resources) =
self.pci_resources(&id, pci_segment_id)?;
info!("Creating pvmemcontrol device: id = {}", id);
let (pvmemcontrol_pci_device, pvmemcontrol_bus_device) =
devices::pvmemcontrol::PvmemcontrolDevice::make_device(
id.clone(),
self.memory_manager.lock().unwrap().guest_memory(),
);
let pvmemcontrol_pci_device = Arc::new(Mutex::new(pvmemcontrol_pci_device));
let pvmemcontrol_bus_device = Arc::new(pvmemcontrol_bus_device);
let new_resources = self.add_pci_device(
pvmemcontrol_bus_device.clone(),
pvmemcontrol_pci_device.clone(),
pci_segment_id,
pci_device_bdf,
resources,
)?;
let mut node = device_node!(id, pvmemcontrol_pci_device);
node.resources = new_resources;
node.pci_bdf = Some(pci_device_bdf);
node.pci_device_handle = None;
self.device_tree.lock().unwrap().insert(id, node);
Ok((pvmemcontrol_bus_device, pvmemcontrol_pci_device))
}
fn make_virtio_balloon_devices(&mut self) -> DeviceManagerResult<Vec<MetaVirtioDevice>> {
let mut devices = Vec::new();

View File

@@ -2230,6 +2230,8 @@ mod unit_tests {
user_devices: None,
vdpa: None,
vsock: None,
#[cfg(feature = "pvmemcontrol")]
pvmemcontrol: None,
pvpanic: false,
iommu: false,
#[cfg(target_arch = "x86_64")]

View File

@@ -424,6 +424,10 @@ pub struct BalloonConfig {
pub free_page_reporting: bool,
}
#[cfg(feature = "pvmemcontrol")]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct PvmemcontrolConfig {}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct FsConfig {
pub tag: String,
@@ -775,6 +779,9 @@ pub struct VmConfig {
pub user_devices: Option<Vec<UserDeviceConfig>>,
pub vdpa: Option<Vec<VdpaConfig>>,
pub vsock: Option<VsockConfig>,
#[cfg(feature = "pvmemcontrol")]
#[serde(default)]
pub pvmemcontrol: Option<PvmemcontrolConfig>,
#[serde(default)]
pub pvpanic: bool,
#[serde(default)]