blob: f6dfab0a83f45105799577438bfcd445729e7ee4 [file] [log] [blame]
// Copyright 2017 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![cfg_attr(windows, allow(unused))]
//! Emulates virtual and hardware devices.
pub mod ac_adapter;
pub mod acpi;
pub mod bat;
mod bus;
#[cfg(feature = "stats")]
mod bus_stats;
pub mod cmos;
#[cfg(target_arch = "x86_64")]
mod debugcon;
mod fw_cfg;
mod i8042;
mod irq_event;
pub mod irqchip;
mod pci;
mod pflash;
pub mod pl030;
pub mod pmc_virt;
mod serial;
pub mod serial_device;
mod suspendable;
mod sys;
#[cfg(any(target_os = "android", target_os = "linux"))]
mod virtcpufreq;
pub mod virtio;
#[cfg(feature = "vtpm")]
mod vtpm_proxy;
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
mod pit;
pub use self::pit::{Pit, PitError};
pub mod tsc;
}
}
use std::sync::Arc;
use anyhow::anyhow;
use anyhow::Context;
use base::debug;
use base::error;
use base::info;
use base::Tube;
use base::TubeError;
use cros_async::AsyncTube;
use cros_async::Executor;
use vm_control::DeviceControlCommand;
use vm_control::DevicesState;
use vm_control::VmResponse;
use vm_memory::GuestMemory;
pub use self::acpi::ACPIPMFixedEvent;
pub use self::acpi::ACPIPMResource;
pub use self::bat::BatteryError;
pub use self::bat::GoldfishBattery;
pub use self::bus::Bus;
pub use self::bus::BusAccessInfo;
pub use self::bus::BusDevice;
pub use self::bus::BusDeviceObj;
pub use self::bus::BusDeviceSync;
pub use self::bus::BusRange;
pub use self::bus::BusResumeDevice;
pub use self::bus::BusType;
pub use self::bus::Error as BusError;
pub use self::bus::HotPlugBus;
pub use self::bus::HotPlugKey;
#[cfg(feature = "stats")]
pub use self::bus_stats::BusStatistics;
#[cfg(target_arch = "x86_64")]
pub use self::debugcon::Debugcon;
pub use self::fw_cfg::Error as FwCfgError;
pub use self::fw_cfg::FwCfgDevice;
pub use self::fw_cfg::FwCfgItemType;
pub use self::fw_cfg::FwCfgParameters;
pub use self::fw_cfg::FW_CFG_BASE_PORT;
pub use self::fw_cfg::FW_CFG_MAX_FILE_SLOTS;
pub use self::fw_cfg::FW_CFG_WIDTH;
pub use self::i8042::I8042Device;
pub use self::irq_event::IrqEdgeEvent;
pub use self::irq_event::IrqLevelEvent;
pub use self::irqchip::*;
pub use self::pci::BarRange;
pub use self::pci::CrosvmDeviceId;
pub use self::pci::GpeScope;
#[cfg(feature = "pci-hotplug")]
pub use self::pci::HotPluggable;
#[cfg(feature = "pci-hotplug")]
pub use self::pci::IntxParameter;
#[cfg(feature = "pci-hotplug")]
pub use self::pci::NetResourceCarrier;
pub use self::pci::PciAddress;
pub use self::pci::PciAddressError;
pub use self::pci::PciBarConfiguration;
pub use self::pci::PciBarIndex;
pub use self::pci::PciBus;
pub use self::pci::PciClassCode;
pub use self::pci::PciConfigIo;
pub use self::pci::PciConfigMmio;
pub use self::pci::PciDevice;
pub use self::pci::PciDeviceError;
pub use self::pci::PciInterruptPin;
pub use self::pci::PciMmioMapper;
pub use self::pci::PciRoot;
pub use self::pci::PciRootCommand;
pub use self::pci::PciVirtualConfigMmio;
pub use self::pci::PreferredIrq;
#[cfg(feature = "pci-hotplug")]
pub use self::pci::ResourceCarrier;
pub use self::pci::StubPciDevice;
pub use self::pci::StubPciParameters;
pub use self::pflash::Pflash;
pub use self::pflash::PflashParameters;
pub use self::pl030::Pl030;
pub use self::serial::Serial;
pub use self::serial_device::Error as SerialError;
pub use self::serial_device::SerialDevice;
pub use self::serial_device::SerialHardware;
pub use self::serial_device::SerialParameters;
pub use self::serial_device::SerialType;
pub use self::suspendable::DeviceState;
pub use self::suspendable::Suspendable;
#[cfg(any(target_os = "android", target_os = "linux"))]
pub use self::virtcpufreq::VirtCpufreq;
pub use self::virtio::VirtioMmioDevice;
pub use self::virtio::VirtioPciDevice;
#[cfg(feature = "vtpm")]
pub use self::vtpm_proxy::VtpmProxy;
cfg_if::cfg_if! {
if #[cfg(any(target_os = "android", target_os = "linux"))] {
mod platform;
mod proxy;
pub mod vmwdt;
pub mod vfio;
#[cfg(feature = "usb")]
#[macro_use]
mod register_space;
#[cfg(feature = "usb")]
pub mod usb;
#[cfg(feature = "usb")]
mod utils;
pub use self::pci::{
CoIommuDev, CoIommuParameters, CoIommuUnpinPolicy, PciBridge, PcieDownstreamPort,
PcieHostPort, PcieRootPort, PcieUpstreamPort, PvPanicCode, PvPanicPciDevice,
VfioPciDevice,
};
pub use self::platform::VfioPlatformDevice;
pub use self::ac_adapter::AcAdapter;
pub use self::pmc_virt::VirtualPmc;
pub use self::proxy::ChildProcIntf;
pub use self::proxy::Error as ProxyError;
pub use self::proxy::ProxyDevice;
#[cfg(feature = "usb")]
pub use self::usb::backend::device_provider::DeviceProvider;
#[cfg(feature = "usb")]
pub use self::usb::xhci::xhci_controller::XhciController;
pub use self::vfio::VfioContainer;
pub use self::vfio::VfioDevice;
pub use self::vfio::VfioDeviceType;
pub use self::virtio::vfio_wrapper;
} else if #[cfg(windows)] {
} else {
compile_error!("Unsupported platform");
}
}
/// Request CoIOMMU to unpin a specific range.
use serde::Deserialize;
/// Request CoIOMMU to unpin a specific range.
use serde::Serialize;
#[derive(Serialize, Deserialize, Debug)]
pub struct UnpinRequest {
/// The ranges presents (start gfn, count).
ranges: Vec<(u64, u64)>,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum UnpinResponse {
Success,
Failed,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
pub enum IommuDevType {
#[serde(rename = "off")]
#[default]
NoIommu,
#[serde(rename = "viommu")]
VirtioIommu,
#[serde(rename = "coiommu")]
CoIommu,
#[serde(rename = "pkvm-iommu")]
PkvmPviommu,
}
// Thread that handles commands sent to devices - such as snapshot, sleep, suspend
// Created when the VM is first created, and re-created on resumption of the VM.
pub fn create_devices_worker_thread(
guest_memory: GuestMemory,
io_bus: Arc<Bus>,
mmio_bus: Arc<Bus>,
device_ctrl_resp: Tube,
) -> std::io::Result<std::thread::JoinHandle<()>> {
std::thread::Builder::new()
.name("device_control".to_string())
.spawn(move || {
let ex = Executor::new().expect("Failed to create an executor");
let async_control = AsyncTube::new(&ex, device_ctrl_resp).unwrap();
match ex.run_until(async move {
handle_command_tube(async_control, guest_memory, io_bus, mmio_bus).await
}) {
Ok(_) => {}
Err(e) => {
error!("Device control thread exited with error: {}", e);
}
};
})
}
fn sleep_buses(buses: &[&Bus]) -> anyhow::Result<()> {
for bus in buses {
bus.sleep_devices()
.with_context(|| format!("failed to sleep devices on {:?} bus", bus.get_bus_type()))?;
debug!("Devices slept successfully on {:?} bus", bus.get_bus_type());
}
Ok(())
}
fn wake_buses(buses: &[&Bus]) {
for bus in buses {
bus.wake_devices()
.with_context(|| format!("failed to wake devices on {:?} bus", bus.get_bus_type()))
// Some devices may have slept. Eternally.
// Recovery - impossible.
// Shut down VM.
.expect("VM panicked to avoid unexpected behavior");
debug!(
"Devices awoken successfully on {:?} Bus",
bus.get_bus_type()
);
}
}
// Use 64MB chunks when writing the memory snapshot (if encryption is used).
const MEMORY_SNAP_ENCRYPTED_CHUNK_SIZE_BYTES: usize = 1024 * 1024 * 64;
async fn snapshot_handler(
snapshot_writer: vm_control::SnapshotWriter,
guest_memory: &GuestMemory,
buses: &[&Bus],
compress_memory: bool,
) -> anyhow::Result<()> {
// SAFETY:
// VM & devices are stopped.
let guest_memory_metadata = unsafe {
guest_memory
.snapshot(
&mut snapshot_writer
.raw_fragment_with_chunk_size("mem", MEMORY_SNAP_ENCRYPTED_CHUNK_SIZE_BYTES)?,
compress_memory,
)
.context("failed to snapshot memory")?
};
snapshot_writer.write_fragment("mem_metadata", &guest_memory_metadata)?;
for (i, bus) in buses.iter().enumerate() {
bus.snapshot_devices(&snapshot_writer.add_namespace(&format!("bus{i}"))?)
.context("failed to snapshot bus devices")?;
debug!(
"Devices snapshot successfully for {:?} Bus",
bus.get_bus_type()
);
}
Ok(())
}
async fn restore_handler(
snapshot_reader: vm_control::SnapshotReader,
guest_memory: &GuestMemory,
buses: &[&Bus],
) -> anyhow::Result<()> {
let guest_memory_metadata = snapshot_reader.read_fragment("mem_metadata")?;
// SAFETY:
// VM & devices are stopped.
unsafe {
guest_memory.restore(
guest_memory_metadata,
&mut snapshot_reader.raw_fragment("mem")?,
)?
};
for (i, bus) in buses.iter().enumerate() {
bus.restore_devices(&snapshot_reader.namespace(&format!("bus{i}"))?)
.context("failed to restore bus devices")?;
debug!(
"Devices restore successfully for {:?} Bus",
bus.get_bus_type()
);
}
Ok(())
}
async fn handle_command_tube(
command_tube: AsyncTube,
guest_memory: GuestMemory,
io_bus: Arc<Bus>,
mmio_bus: Arc<Bus>,
) -> anyhow::Result<()> {
let buses = &[&*io_bus, &*mmio_bus];
// We assume devices are awake. This is safe because if the VM starts the
// sleeping state, run_control will ask us to sleep devices.
let mut devices_state = DevicesState::Wake;
loop {
match command_tube.next().await {
Ok(command) => {
match command {
DeviceControlCommand::SleepDevices => {
if let DevicesState::Wake = devices_state {
match sleep_buses(buses) {
Ok(()) => {
devices_state = DevicesState::Sleep;
}
Err(e) => {
error!("failed to sleep: {:#}", e);
// Failing to sleep could mean a single device failing to sleep.
// Wake up devices to resume functionality of the VM.
info!("Attempting to wake devices after failed sleep");
wake_buses(buses);
command_tube
.send(VmResponse::ErrString(e.to_string()))
.await
.context("failed to send response.")?;
continue;
}
}
}
command_tube
.send(VmResponse::Ok)
.await
.context("failed to reply to sleep command")?;
}
DeviceControlCommand::WakeDevices => {
if let DevicesState::Sleep = devices_state {
wake_buses(buses);
devices_state = DevicesState::Wake;
}
command_tube
.send(VmResponse::Ok)
.await
.context("failed to reply to wake devices request")?;
}
DeviceControlCommand::SnapshotDevices {
snapshot_writer,
compress_memory,
} => {
assert!(
matches!(devices_state, DevicesState::Sleep),
"devices must be sleeping to snapshot"
);
if let Err(e) =
snapshot_handler(snapshot_writer, &guest_memory, buses, compress_memory)
.await
{
error!("failed to snapshot: {:#}", e);
command_tube
.send(VmResponse::ErrString(e.to_string()))
.await
.context("Failed to send response")?;
continue;
}
command_tube
.send(VmResponse::Ok)
.await
.context("Failed to send response")?;
}
DeviceControlCommand::RestoreDevices { snapshot_reader } => {
assert!(
matches!(devices_state, DevicesState::Sleep),
"devices must be sleeping to restore"
);
if let Err(e) =
restore_handler(snapshot_reader, &guest_memory, &[&*io_bus, &*mmio_bus])
.await
{
error!("failed to restore: {:#}", e);
command_tube
.send(VmResponse::ErrString(e.to_string()))
.await
.context("Failed to send response")?;
continue;
}
command_tube
.send(VmResponse::Ok)
.await
.context("Failed to send response")?;
}
DeviceControlCommand::GetDevicesState => {
command_tube
.send(VmResponse::DevicesState(devices_state.clone()))
.await
.context("failed to send response")?;
}
DeviceControlCommand::Exit => {
return Ok(());
}
};
}
Err(e) => {
if matches!(e, TubeError::Disconnected) {
// Tube disconnected - shut down thread.
return Ok(());
}
return Err(anyhow!("Failed to receive: {}", e));
}
}
}
}