mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-10 04:07:23 +00:00
crosvm: upstream windows src
- Upstreams all windows specific files in src/ - Adds windows specific args to Config/Command parsing. - Adds noop anti tamper crate. There are still some deltas between upstream and downstream src because of moving HEAD in upstream and some code refactors downstream. But this is most of the code. BUG=b:213146388 TEST=built on windows downstream. upstream crosvm does not build on windows yet because of to-be-upstreamed dependency crates. presubmit. Change-Id: I3445975749f8108ae51d5fb6e1c2f1447439e1fb Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3765346 Commit-Queue: Vikram Auradkar <auradkar@google.com> Auto-Submit: Vikram Auradkar <auradkar@google.com> Tested-by: Vikram Auradkar <auradkar@google.com> Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
parent
46b9f75a36
commit
ce5172c899
23 changed files with 7446 additions and 15 deletions
|
@ -43,6 +43,7 @@ lto = true
|
|||
members = [
|
||||
"aarch64",
|
||||
"acpi_tables",
|
||||
"anti_tamper",
|
||||
"arch",
|
||||
"argh_helpers",
|
||||
"base",
|
||||
|
|
14
anti_tamper/Cargo.toml
Normal file
14
anti_tamper/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "anti_tamper"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
anti-tamper = []
|
||||
proto-tube-hack = []
|
||||
|
||||
[dependencies]
|
||||
base = { path = "../base" }
|
7
anti_tamper/src/lib.rs
Normal file
7
anti_tamper/src/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
pub mod noop;
|
||||
|
||||
pub use noop::*;
|
35
anti_tamper/src/noop.rs
Normal file
35
anti_tamper/src/noop.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::thread;
|
||||
|
||||
use base::Tube;
|
||||
|
||||
pub fn setup_common_metric_invariants(
|
||||
_product_version: &Option<String>,
|
||||
_product_channel: &Option<String>,
|
||||
_use_vulkan: &Option<bool>,
|
||||
) {
|
||||
}
|
||||
|
||||
#[cfg(feature = "proto-tube-hack")]
|
||||
pub fn forward_security_challenge(_recv: &Tube, _sender: &Tube) {}
|
||||
|
||||
#[cfg(feature = "proto-tube-hack")]
|
||||
pub fn forward_security_signal(_recv: &Tube, _sender: &Tube) {}
|
||||
|
||||
pub fn enable_vcpu_monitoring() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// This is a hard limit as it is used to set the Tube buffer size, and will
|
||||
// deadlock if exceeded (b/223807352).
|
||||
pub const MAX_CHALLENGE_SIZE: usize = 1;
|
||||
|
||||
pub fn spawn_dedicated_anti_tamper_thread(
|
||||
#[cfg(not(feature = "proto-tube-hack"))] _tube_to_main_thread: Tube,
|
||||
#[cfg(feature = "proto-tube-hack")] _tube_to_main_thread: base::Tube,
|
||||
) -> thread::JoinHandle<()> {
|
||||
thread::spawn(move || {})
|
||||
}
|
|
@ -17,6 +17,9 @@ cfg_if::cfg_if! {
|
|||
parse_coiommu_params, VfioCommand, parse_vfio, parse_vfio_platform,
|
||||
};
|
||||
use super::config::SharedDir;
|
||||
} else if #[cfg(windows)] {
|
||||
use crate::crosvm::sys::config::IrqChipKind;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -514,6 +517,10 @@ pub struct RunCommand {
|
|||
/// num_input_streams=INT - Set number of input PCM streams
|
||||
/// per device.
|
||||
pub cras_snds: Vec<SndParameters>,
|
||||
#[cfg(feature = "crash-report")]
|
||||
#[argh(option, long = "crash-pipe-name", arg_name = "\\\\.\\pipe\\PIPE_NAME")]
|
||||
/// the crash handler ipc pipe name.
|
||||
pub crash_pipe_name: Option<String>,
|
||||
#[argh(switch)]
|
||||
/// don't set VCPUs real-time until make-rt command is run
|
||||
pub delay_rt: bool,
|
||||
|
@ -584,6 +591,10 @@ pub struct RunCommand {
|
|||
#[argh(positional, arg_name = "KERNEL")]
|
||||
/// bzImage of kernel to run
|
||||
pub executable_path: Option<PathBuf>,
|
||||
#[cfg(windows)]
|
||||
#[argh(switch, long = "exit-stats")]
|
||||
/// gather and display statistics on Vm Exits and Bus Reads/Writes.
|
||||
pub exit_stats: bool,
|
||||
#[argh(
|
||||
option,
|
||||
long = "file-backed-mapping",
|
||||
|
@ -674,6 +685,10 @@ pub struct RunCommand {
|
|||
#[argh(switch)]
|
||||
/// use mirror cpu topology of Host for Guest VM, also copy some cpu feature to Guest VM
|
||||
pub host_cpu_topology: bool,
|
||||
#[cfg(windows)]
|
||||
#[argh(option, long = "host-guid", arg_name = "PATH")]
|
||||
/// string representation of the host guid in registry format, for namespacing vsock connections.
|
||||
pub host_guid: Option<String>,
|
||||
#[cfg(unix)]
|
||||
#[argh(option, arg_name = "IP")]
|
||||
/// IP address to assign to host tap interface
|
||||
|
@ -687,9 +702,17 @@ pub struct RunCommand {
|
|||
#[argh(option, short = 'i', long = "initrd", arg_name = "PATH")]
|
||||
/// initial ramdisk to load
|
||||
pub initrd_path: Option<PathBuf>,
|
||||
#[cfg(windows)]
|
||||
#[argh(option, long = "irqchip", arg_name = "kernel|split|userspace")]
|
||||
/// type of interrupt controller emulation. \"split\" is only available for x86 KVM.
|
||||
pub irq_chip: Option<IrqChipKind>,
|
||||
#[argh(switch)]
|
||||
/// allow to enable ITMT scheduling feature in VM. The success of enabling depends on HWP and ACPI CPPC support on hardware
|
||||
pub itmt: bool,
|
||||
#[cfg(windows)]
|
||||
#[argh(option, long = "kernel-log-file", arg_name = "PATH")]
|
||||
/// forward hypervisor kernel driver logs for this VM to a file.
|
||||
pub kernel_log_file: Option<String>,
|
||||
#[cfg(unix)]
|
||||
#[argh(option, long = "kvm-device", arg_name = "PATH")]
|
||||
/// path to the KVM device. (default /dev/kvm)
|
||||
|
@ -698,6 +721,14 @@ pub struct RunCommand {
|
|||
#[argh(switch)]
|
||||
/// disable host swap on guest VM pages.
|
||||
pub lock_guest_memory: bool,
|
||||
#[cfg(windows)]
|
||||
#[argh(option, long = "log-file", arg_name = "PATH")]
|
||||
/// redirect logs to the supplied log file at PATH rather than stderr. For multi-process mode, use --logs-directory instead
|
||||
pub log_file: Option<String>,
|
||||
#[cfg(windows)]
|
||||
#[argh(option, long = "logs-directory", arg_name = "PATH")]
|
||||
/// path to the logs directory used for crosvm processes. Logs will be sent to stderr if unset, and stderr/stdout will be uncaptured
|
||||
pub logs_directory: Option<String>,
|
||||
#[cfg(unix)]
|
||||
#[argh(option, arg_name = "MAC", long = "mac")]
|
||||
/// MAC address for VM
|
||||
|
@ -802,6 +833,26 @@ pub struct RunCommand {
|
|||
#[argh(switch)]
|
||||
/// grant this Guest VM certian privileges to manage Host resources, such as power management
|
||||
pub privileged_vm: bool,
|
||||
#[cfg(feature = "process-invariants")]
|
||||
#[argh(option, long = "process-invariants-handle", arg_name = "PATH")]
|
||||
/// shared read-only memory address for a serialized EmulatorProcessInvariants proto
|
||||
pub process_invariants_data_handle: Option<u64>,
|
||||
#[cfg(feature = "process-invariants")]
|
||||
#[argh(option, long = "process-invariants-size", arg_name = "PATH")]
|
||||
/// size of the serialized EmulatorProcessInvariants proto pointed at by process-invariants-handle
|
||||
pub process_invariants_data_size: Option<usize>,
|
||||
#[cfg(windows)]
|
||||
#[argh(option, long = "product-channel")]
|
||||
/// product channel
|
||||
pub product_channel: Option<String>,
|
||||
#[cfg(feature = "crash-report")]
|
||||
#[argh(option, long = "product-name")]
|
||||
/// the product name for file paths.
|
||||
pub product_name: Option<String>,
|
||||
#[cfg(windows)]
|
||||
#[argh(option, long = "product-version")]
|
||||
/// product version
|
||||
pub product_version: Option<String>,
|
||||
#[argh(switch)]
|
||||
/// prevent host access to guest memory
|
||||
pub protected_vm: bool,
|
||||
|
@ -812,6 +863,10 @@ pub struct RunCommand {
|
|||
/// path to pstore buffer backend file followed by size
|
||||
/// [--pstore <path=PATH,size=SIZE>]
|
||||
pub pstore: Option<Pstore>,
|
||||
#[cfg(windows)]
|
||||
#[argh(switch)]
|
||||
/// enable virtio-pvclock.
|
||||
pub pvclock: bool,
|
||||
// Must be `Some` iff `protected_vm == ProtectionType::UnprotectedWithFirmware`.
|
||||
#[argh(option, long = "unprotected-vm-with-firmware", arg_name = "PATH")]
|
||||
/// (EXPERIMENTAL/FOR DEBUGGING) Use VM firmware, but allow host access to guest memory
|
||||
|
@ -913,6 +968,10 @@ pub struct RunCommand {
|
|||
/// Can only be given once. Will default to first serial
|
||||
/// port if not provided.
|
||||
pub serial_parameters: Vec<SerialParameters>,
|
||||
#[cfg(feature = "kiwi")]
|
||||
#[argh(option, long = "service-pipe-name", arg_name = "PIPE_NAME")]
|
||||
/// the service ipc pipe name. (Prefix \\\\.\\pipe\\ not needed.
|
||||
pub service_pipe_name: Option<String>,
|
||||
#[cfg(unix)]
|
||||
#[argh(
|
||||
option,
|
||||
|
@ -966,6 +1025,10 @@ pub struct RunCommand {
|
|||
/// when the underlying file system supports POSIX ACLs.
|
||||
/// The default value for this option is "true".
|
||||
pub shared_dirs: Vec<SharedDir>,
|
||||
#[cfg(feature = "slirp-ring-capture")]
|
||||
#[argh(option, long = "slirp-capture-file", arg_name = "PATH")]
|
||||
/// Redirects slirp network packets to the supplied log file rather than the current directory as `slirp_capture_packets.pcap`
|
||||
pub slirp_capture_file: Option<String>,
|
||||
#[argh(option, short = 's', long = "socket", arg_name = "PATH")]
|
||||
/// path to put the control socket. If PATH is a directory, a name will be generated
|
||||
pub socket_path: Option<PathBuf>,
|
||||
|
@ -1391,6 +1454,36 @@ impl TryFrom<RunCommand> for super::config::Config {
|
|||
cfg.pmem_devices.push(pmem);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
#[cfg(feature = "crash-report")]
|
||||
{
|
||||
cfg.product_name = cmd.product_name;
|
||||
|
||||
cfg.crash_pipe_name = cmd.crash_pipe_name;
|
||||
}
|
||||
cfg.exit_stats = cmd.exit_stats;
|
||||
cfg.host_guid = cmd.host_guid;
|
||||
cfg.irq_chip = cmd.irq_chip;
|
||||
cfg.kernel_log_file = cmd.kernel_log_file;
|
||||
cfg.log_file = cmd.log_file;
|
||||
cfg.logs_directory = cmd.logs_directory;
|
||||
#[cfg(feature = "process-invariants")]
|
||||
{
|
||||
cfg.process_invariants_data_handle = cmd.process_invariants_data_handle;
|
||||
|
||||
cfg.process_invariants_data_size = cmd.process_invariants_data_size;
|
||||
}
|
||||
cfg.pvclock = cmd.pvclock;
|
||||
cfg.service_pipe_name = cmd.service_pipe_name;
|
||||
#[cfg(feature = "slirp-ring-capture")]
|
||||
{
|
||||
cfg.slirp_capture_file = cmd.slirp_capture_file;
|
||||
}
|
||||
cfg.syslog_tag = cmd.syslog_tag;
|
||||
cfg.product_channel = cmd.product_channel;
|
||||
cfg.product_version = cmd.product_version;
|
||||
}
|
||||
cfg.pstore = cmd.pstore;
|
||||
|
||||
#[cfg(unix)]
|
||||
|
|
|
@ -35,7 +35,7 @@ use x86_64::{set_enable_pnp_data_msr_config, set_itmt_msr_config};
|
|||
#[cfg(feature = "audio")]
|
||||
use devices::{Ac97Backend, Ac97Parameters};
|
||||
|
||||
use super::{argument::parse_hex_or_decimal, check_opt_path};
|
||||
use super::{argument::parse_hex_or_decimal, check_opt_path, sys::HypervisorKind};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(unix)] {
|
||||
|
@ -49,6 +49,10 @@ cfg_if::cfg_if! {
|
|||
static KVM_PATH: &str = "/dev/kvm";
|
||||
static VHOST_NET_PATH: &str = "/dev/vhost-net";
|
||||
static SECCOMP_POLICY_DIR: &str = "/usr/share/policy/crosvm";
|
||||
} else if #[cfg(windows)] {
|
||||
use base::{Event, Tube};
|
||||
|
||||
use crate::crosvm::sys::windows::config::IrqChipKind;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1236,11 +1240,21 @@ pub struct Config {
|
|||
pub balloon_bias: i64,
|
||||
pub balloon_control: Option<PathBuf>,
|
||||
pub battery_type: Option<BatteryType>,
|
||||
#[cfg(windows)]
|
||||
pub block_control_tube: Vec<Tube>,
|
||||
#[cfg(windows)]
|
||||
pub block_vhost_user_tube: Vec<Tube>,
|
||||
#[cfg(windows)]
|
||||
pub broker_shutdown_event: Option<Event>,
|
||||
pub cid: Option<u64>,
|
||||
#[cfg(unix)]
|
||||
pub coiommu_param: Option<devices::CoIommuParameters>,
|
||||
pub cpu_capacity: BTreeMap<usize, u32>, // CPU index -> capacity
|
||||
pub cpu_clusters: Vec<Vec<usize>>,
|
||||
#[cfg(feature = "crash-report")]
|
||||
pub crash_pipe_name: Option<String>,
|
||||
#[cfg(feature = "crash-report")]
|
||||
pub crash_report_uuid: Option<String>,
|
||||
pub delay_rt: bool,
|
||||
#[cfg(feature = "direct")]
|
||||
pub direct_edge_irq: Vec<u32>,
|
||||
|
@ -1259,6 +1273,8 @@ pub struct Config {
|
|||
pub dmi_path: Option<PathBuf>,
|
||||
pub enable_pnp_data: bool,
|
||||
pub executable_path: Option<Executable>,
|
||||
#[cfg(windows)]
|
||||
pub exit_stats: bool,
|
||||
pub file_backed_mappings: Vec<FileBackedMappingParameters>,
|
||||
pub force_calibrated_tsc_leaf: bool,
|
||||
pub force_s2idle: bool,
|
||||
|
@ -1269,20 +1285,33 @@ pub struct Config {
|
|||
#[cfg(all(unix, feature = "gpu"))]
|
||||
pub gpu_render_server_parameters: Option<GpuRenderServerParameters>,
|
||||
pub host_cpu_topology: bool,
|
||||
#[cfg(windows)]
|
||||
pub host_guid: Option<String>,
|
||||
pub host_ip: Option<net::Ipv4Addr>,
|
||||
pub hugepages: bool,
|
||||
pub hypervisor: Option<HypervisorKind>,
|
||||
pub init_memory: Option<u64>,
|
||||
pub initrd_path: Option<PathBuf>,
|
||||
#[cfg(windows)]
|
||||
pub irq_chip: Option<IrqChipKind>,
|
||||
pub itmt: bool,
|
||||
pub jail_config: Option<JailConfig>,
|
||||
#[cfg(windows)]
|
||||
pub kernel_log_file: Option<String>,
|
||||
#[cfg(unix)]
|
||||
pub kvm_device_path: PathBuf,
|
||||
#[cfg(unix)]
|
||||
pub lock_guest_memory: bool,
|
||||
#[cfg(windows)]
|
||||
pub log_file: Option<String>,
|
||||
#[cfg(windows)]
|
||||
pub logs_directory: Option<String>,
|
||||
pub mac_address: Option<net_util::MacAddress>,
|
||||
pub memory: Option<u64>,
|
||||
pub memory_file: Option<PathBuf>,
|
||||
pub mmio_address_ranges: Vec<AddressRange>,
|
||||
#[cfg(windows)]
|
||||
pub net_vhost_user_tube: Option<Tube>,
|
||||
pub net_vq_pairs: Option<u16>,
|
||||
pub netmask: Option<net::Ipv4Addr>,
|
||||
pub no_i8042: bool,
|
||||
|
@ -1302,17 +1331,33 @@ pub struct Config {
|
|||
pub plugin_root: Option<PathBuf>,
|
||||
pub pmem_devices: Vec<DiskOption>,
|
||||
pub privileged_vm: bool,
|
||||
#[cfg(feature = "process-invariants")]
|
||||
pub process_invariants_data_handle: Option<u64>,
|
||||
#[cfg(feature = "process-invariants")]
|
||||
pub process_invariants_data_size: Option<usize>,
|
||||
#[cfg(feature = "crash-report")]
|
||||
pub product_channel: Option<String>,
|
||||
#[cfg(windows)]
|
||||
pub product_name: Option<String>,
|
||||
#[cfg(windows)]
|
||||
pub product_version: Option<String>,
|
||||
pub protected_vm: ProtectionType,
|
||||
pub pstore: Option<Pstore>,
|
||||
#[cfg(windows)]
|
||||
pub pvclock: bool,
|
||||
/// Must be `Some` iff `protected_vm == ProtectionType::UnprotectedWithFirmware`.
|
||||
pub pvm_fw: Option<PathBuf>,
|
||||
pub rng: bool,
|
||||
pub rt_cpus: Vec<usize>,
|
||||
#[serde(with = "serde_serial_params")]
|
||||
pub serial_parameters: BTreeMap<(SerialHardware, u8), SerialParameters>,
|
||||
#[cfg(feature = "kiwi")]
|
||||
pub service_pipe_name: Option<String>,
|
||||
#[cfg(unix)]
|
||||
#[serde(skip)]
|
||||
pub shared_dirs: Vec<SharedDir>,
|
||||
#[cfg(feature = "slirp-ring-capture")]
|
||||
pub slirp_capture_file: Option<String>,
|
||||
pub socket_path: Option<PathBuf>,
|
||||
#[cfg(feature = "tpm")]
|
||||
pub software_tpm: bool,
|
||||
|
@ -1322,6 +1367,8 @@ pub struct Config {
|
|||
pub strict_balloon: bool,
|
||||
pub stub_pci_devices: Vec<StubPciParameters>,
|
||||
pub swiotlb: Option<u64>,
|
||||
#[cfg(windows)]
|
||||
pub syslog_tag: Option<String>,
|
||||
#[cfg(unix)]
|
||||
pub tap_fd: Vec<RawDescriptor>,
|
||||
pub tap_name: Vec<String>,
|
||||
|
@ -1381,9 +1428,19 @@ impl Default for Config {
|
|||
balloon_bias: 0,
|
||||
balloon_control: None,
|
||||
battery_type: None,
|
||||
#[cfg(windows)]
|
||||
block_control_tube: Vec::new(),
|
||||
#[cfg(windows)]
|
||||
block_vhost_user_tube: Vec::new(),
|
||||
#[cfg(windows)]
|
||||
broker_shutdown_event: None,
|
||||
cid: None,
|
||||
#[cfg(unix)]
|
||||
coiommu_param: None,
|
||||
#[cfg(feature = "crash-report")]
|
||||
crash_pipe_name: None,
|
||||
#[cfg(feature = "crash-report")]
|
||||
crash_report_uuid: None,
|
||||
cpu_capacity: BTreeMap::new(),
|
||||
cpu_clusters: Vec::new(),
|
||||
delay_rt: false,
|
||||
|
@ -1404,6 +1461,8 @@ impl Default for Config {
|
|||
dmi_path: None,
|
||||
enable_pnp_data: false,
|
||||
executable_path: None,
|
||||
#[cfg(windows)]
|
||||
exit_stats: false,
|
||||
file_backed_mappings: Vec::new(),
|
||||
force_calibrated_tsc_leaf: false,
|
||||
force_s2idle: false,
|
||||
|
@ -1414,24 +1473,41 @@ impl Default for Config {
|
|||
#[cfg(all(unix, feature = "gpu"))]
|
||||
gpu_render_server_parameters: None,
|
||||
host_cpu_topology: false,
|
||||
#[cfg(windows)]
|
||||
host_guid: None,
|
||||
host_ip: None,
|
||||
#[cfg(windows)]
|
||||
product_version: None,
|
||||
#[cfg(windows)]
|
||||
product_channel: None,
|
||||
hugepages: false,
|
||||
hypervisor: None,
|
||||
init_memory: None,
|
||||
initrd_path: None,
|
||||
#[cfg(windows)]
|
||||
irq_chip: None,
|
||||
itmt: false,
|
||||
jail_config: if !cfg!(feature = "default-no-sandbox") {
|
||||
Some(Default::default())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
#[cfg(windows)]
|
||||
kernel_log_file: None,
|
||||
#[cfg(unix)]
|
||||
kvm_device_path: PathBuf::from(KVM_PATH),
|
||||
#[cfg(unix)]
|
||||
lock_guest_memory: false,
|
||||
#[cfg(windows)]
|
||||
log_file: None,
|
||||
#[cfg(windows)]
|
||||
logs_directory: None,
|
||||
mac_address: None,
|
||||
memory: None,
|
||||
memory_file: None,
|
||||
mmio_address_ranges: Vec::new(),
|
||||
#[cfg(windows)]
|
||||
net_vhost_user_tube: None,
|
||||
net_vq_pairs: None,
|
||||
netmask: None,
|
||||
no_i8042: false,
|
||||
|
@ -1451,14 +1527,26 @@ impl Default for Config {
|
|||
plugin_root: None,
|
||||
pmem_devices: Vec::new(),
|
||||
privileged_vm: false,
|
||||
#[cfg(feature = "process-invariants")]
|
||||
process_invariants_data_handle: None,
|
||||
#[cfg(feature = "process-invariants")]
|
||||
process_invariants_data_size: None,
|
||||
#[cfg(feature = "crash-report")]
|
||||
product_name: None,
|
||||
protected_vm: ProtectionType::Unprotected,
|
||||
pstore: None,
|
||||
#[cfg(windows)]
|
||||
pvclock: false,
|
||||
pvm_fw: None,
|
||||
rng: true,
|
||||
rt_cpus: Vec::new(),
|
||||
serial_parameters: BTreeMap::new(),
|
||||
#[cfg(feature = "kiwi")]
|
||||
service_pipe_name: None,
|
||||
#[cfg(unix)]
|
||||
shared_dirs: Vec::new(),
|
||||
#[cfg(feature = "slirp-ring-capture")]
|
||||
slirp_capture_file: None,
|
||||
socket_path: None,
|
||||
#[cfg(feature = "tpm")]
|
||||
software_tpm: false,
|
||||
|
@ -1468,6 +1556,8 @@ impl Default for Config {
|
|||
strict_balloon: false,
|
||||
stub_pci_devices: Vec::new(),
|
||||
swiotlb: None,
|
||||
#[cfg(windows)]
|
||||
syslog_tag: None,
|
||||
#[cfg(unix)]
|
||||
tap_fd: Vec::new(),
|
||||
tap_name: Vec::new(),
|
||||
|
|
|
@ -2,11 +2,18 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) mod unix;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(crate) mod windows;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(unix)] {
|
||||
pub(crate) mod unix;
|
||||
use unix as platform;
|
||||
pub(crate) use unix::*;
|
||||
} else if #[cfg(windows)] {
|
||||
use windows as platform;
|
||||
} else {
|
||||
compile_error!("Unsupported platform");
|
||||
}
|
||||
|
@ -15,5 +22,8 @@ cfg_if::cfg_if! {
|
|||
pub(crate) use platform::cmdline;
|
||||
pub(crate) use platform::config;
|
||||
|
||||
#[cfg(feature = "crash-report")]
|
||||
pub(crate) use platform::broker::setup_emulator_crash_reporting;
|
||||
#[cfg(feature = "gpu")]
|
||||
pub(crate) use platform::config::validate_gpu_config;
|
||||
pub(crate) use platform::config::HypervisorKind;
|
||||
|
|
|
@ -16,13 +16,27 @@ use crate::crosvm::config::{invalid_value_err, Config};
|
|||
#[cfg(feature = "gpu")]
|
||||
use crate::crosvm::{argument, argument::parse_hex_or_decimal};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum HypervisorKind {
|
||||
Kvm,
|
||||
}
|
||||
|
||||
impl FromStr for HypervisorKind {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"kvm" => Ok(HypervisorKind::Kvm),
|
||||
_ => Err("invalid hypervisor backend"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
|
||||
pub fn parse_gpu_render_server_options(
|
||||
s: &str,
|
||||
) -> Result<crate::crosvm::sys::GpuRenderServerParameters, String> {
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use crate::crosvm::{config::invalid_value_err, sys::GpuRenderServerParameters};
|
||||
use crate::crosvm::sys::GpuRenderServerParameters;
|
||||
|
||||
let mut path: Option<PathBuf> = None;
|
||||
let mut cache_path = None;
|
||||
|
@ -65,8 +79,6 @@ pub fn parse_ac97_options(
|
|||
key: &str,
|
||||
#[allow(unused_variables)] value: &str,
|
||||
) -> Result<(), String> {
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
match key {
|
||||
#[cfg(feature = "audio_cras")]
|
||||
"client_type" => {
|
||||
|
@ -260,8 +272,6 @@ pub fn parse_gpu_options(s: &str) -> Result<GpuParameters, String> {
|
|||
use devices::virtio::GpuMode;
|
||||
use rutabaga_gfx::RutabagaWsi;
|
||||
|
||||
use crate::crosvm::sys::config::is_gpu_backend_deprecated;
|
||||
|
||||
#[cfg(feature = "gfxstream")]
|
||||
let mut vulkan_specified = false;
|
||||
#[cfg(feature = "gfxstream")]
|
||||
|
|
10
src/crosvm/sys/windows.rs
Normal file
10
src/crosvm/sys/windows.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
pub mod cmdline;
|
||||
pub mod config;
|
||||
|
||||
pub(crate) mod broker;
|
||||
pub(crate) mod exit;
|
||||
pub(crate) mod stats;
|
1747
src/crosvm/sys/windows/broker.rs
Normal file
1747
src/crosvm/sys/windows/broker.rs
Normal file
File diff suppressed because it is too large
Load diff
85
src/crosvm/sys/windows/cmdline.rs
Normal file
85
src/crosvm/sys/windows/cmdline.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use argh::FromArgs;
|
||||
|
||||
use argh_helpers::generate_catchall_args;
|
||||
#[derive(Debug, FromArgs)]
|
||||
#[argh(subcommand)]
|
||||
/// Windows Devices
|
||||
pub enum DevicesSubcommand {}
|
||||
|
||||
#[cfg(feature = "slirp")]
|
||||
#[generate_catchall_args]
|
||||
#[argh(subcommand, name = "run-slirp")]
|
||||
/// Start a new metrics instance
|
||||
pub struct RunSlirpCommand {}
|
||||
|
||||
#[generate_catchall_args]
|
||||
#[argh(subcommand, name = "run-main")]
|
||||
/// Start a new broker instance
|
||||
pub struct RunMainCommand {}
|
||||
|
||||
#[generate_catchall_args]
|
||||
#[argh(subcommand, name = "run-metrics")]
|
||||
/// Start a new metrics instance
|
||||
pub struct RunMetricsCommand {}
|
||||
|
||||
/// Start a new mp crosvm instance
|
||||
#[generate_catchall_args]
|
||||
#[argh(subcommand, name = "run-mp")]
|
||||
pub struct RunMPCommand {}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand)]
|
||||
/// Windows Devices
|
||||
pub enum Commands {
|
||||
RunMetrics(RunMetricsCommand),
|
||||
RunMP(RunMPCommand),
|
||||
#[cfg(feature = "slirp")]
|
||||
RunSlirp(RunSlirpCommand),
|
||||
RunMain(RunMainCommand),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::crosvm::cmdline::RunCommand;
|
||||
|
||||
fn get_args() -> Vec<&'static str> {
|
||||
vec!["--bios", "C:\\src\\crosvm\\out\\image\\default\\images\\bios.rom",
|
||||
"--crash-pipe-name", "\\\\.\\pipe\\crashpad_27812_XGTCCTBYULHHLEJU", "--cpus", "4",
|
||||
"--mem", "8192",
|
||||
"--log-file", "C:\\tmp\\Emulator.log",
|
||||
"--kernel-log-file", "C:\\tmp\\Hypervisor.log",
|
||||
"--logs-directory", "C:\\tmp\\emulator_logs",
|
||||
"--serial", "hardware=serial,num=1,type=file,path=C:\\tmp\\AndroidSerial.log,earlycon=true",
|
||||
"--serial", "hardware=virtio-console,num=1,type=file,path=C:\\tmp\\AndroidSerial.log,console=true",
|
||||
"--rwdisk", "C:\\src\\crosvm\\out\\image\\default\\avd\\aggregate.img",
|
||||
"--rwdisk", "C:\\src\\crosvm\\out\\image\\default\\avd\\metadata.img",
|
||||
"--rwdisk", "C:\\src\\crosvm\\out\\image\\default\\avd\\userdata.img",
|
||||
"--rwdisk", "C:\\src\\crosvm\\out\\image\\default\\avd\\misc.img",
|
||||
"--process-invariants-handle", "7368", "--process-invariants-size", "568",
|
||||
"--gpu", "angle=true,backend=gfxstream,egl=true,gles=false,glx=false,refresh_rate=60,surfaceless=false,vulkan=true,wsi=vk,display_mode=borderless_full_screen,hidden",
|
||||
"--host-guid", "09205719-879f-4324-8efc-3e362a4096f4",
|
||||
"--ac97", "backend=win_audio",
|
||||
"--cid", "3", "--multi-touch", "nil", "--mouse", "nil", "--product-version", "99.9.9.9",
|
||||
"--product-channel", "Local", "--product-name", "Play Games",
|
||||
"--service-pipe-name", "service-ipc-8244a83a-ae3f-486f-9c50-3fc47b309d27",
|
||||
"--pstore", "path=C:\\tmp\\pstore,size=1048576",
|
||||
"--pvclock",
|
||||
"--params", "fake args"]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_run_mp_test() {
|
||||
let _ = RunMPCommand::from_args(&[&"run-mp"], &get_args()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_run_test() {
|
||||
let _ = RunCommand::from_args(&[&"run-main"], &get_args()).unwrap();
|
||||
}
|
||||
}
|
822
src/crosvm/sys/windows/config.rs
Normal file
822
src/crosvm/sys/windows/config.rs
Normal file
|
@ -0,0 +1,822 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(feature = "gpu")]
|
||||
use base::info;
|
||||
#[cfg(all(feature = "prod-build", feature = "kiwi"))]
|
||||
use devices::serial_device::SerialType;
|
||||
#[cfg(feature = "gpu")]
|
||||
use devices::virtio::{GpuDisplayMode, GpuDisplayParameters, GpuMode, GpuParameters};
|
||||
use devices::Ac97Parameters;
|
||||
use devices::SerialParameters;
|
||||
use metrics::event_details_proto::EmulatorProcessType;
|
||||
#[cfg(feature = "gpu")]
|
||||
use rutabaga_gfx::{calculate_context_mask, RutabagaWsi};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::crosvm::{argument, config::Config};
|
||||
|
||||
#[cfg(feature = "audio")]
|
||||
pub fn parse_ac97_options(
|
||||
_ac97_params: &mut Ac97Parameters,
|
||||
key: &str,
|
||||
value: &str,
|
||||
) -> Result<(), String> {
|
||||
Err(format!("unknown ac97 parameter {} {}", key, value))
|
||||
}
|
||||
|
||||
#[cfg(feature = "audio")]
|
||||
pub(crate) fn check_ac97_backend(
|
||||
#[allow(unused_variables)] ac97_params: &Ac97Parameters,
|
||||
) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpu")]
|
||||
pub fn is_gpu_backend_deprecated(backend: &str) -> bool {
|
||||
match backend {
|
||||
"2d" | "2D" | "3d" | "3D" | "virglrenderer" => {
|
||||
cfg!(feature = "gfxstream")
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "gfxstream")]
|
||||
pub fn use_vulkan() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn check_serial_params(
|
||||
#[allow(unused_variables)] serial_params: &SerialParameters,
|
||||
) -> Result<(), String> {
|
||||
#[cfg(all(feature = "prod-build", feature = "kiwi"))]
|
||||
{
|
||||
if matches!(serial_params.type_, SerialType::SystemSerialType) {
|
||||
return Err(format!(
|
||||
"device type not supported: {}",
|
||||
serial_params.type_.to_string()
|
||||
));
|
||||
}
|
||||
if serial_params.stdin {
|
||||
return Err(format!("parameter not supported: stdin"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_config(_cfg: &mut Config) -> std::result::Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpu")]
|
||||
pub fn parse_gpu_options(s: &str) -> Result<GpuParameters, String> {
|
||||
parse_gpu_options_inner(s).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpu")]
|
||||
fn parse_gpu_options_inner(s: &str) -> argument::Result<GpuParameters> {
|
||||
let mut gpu_params: GpuParameters = Default::default();
|
||||
#[cfg(feature = "gfxstream")]
|
||||
let mut vulkan_specified = false;
|
||||
#[cfg(feature = "gfxstream")]
|
||||
let mut syncfd_specified = false;
|
||||
#[cfg(feature = "gfxstream")]
|
||||
let mut gles31_specified = false;
|
||||
#[cfg(feature = "gfxstream")]
|
||||
let mut angle_specified = false;
|
||||
|
||||
let mut width: Option<u32> = None;
|
||||
let mut height: Option<u32> = None;
|
||||
let mut dpi: Option<u32> = None;
|
||||
let mut display_mode: Option<String> = None;
|
||||
#[cfg(feature = "gfxstream")]
|
||||
let mut vsync: Option<u32> = None;
|
||||
let opts = s
|
||||
.split(',')
|
||||
.map(|frag| frag.split('='))
|
||||
.map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
|
||||
let mut hidden: Option<bool> = None;
|
||||
|
||||
for (k, v) in opts {
|
||||
match k {
|
||||
"backend" => match v {
|
||||
"2d" | "2D" => {
|
||||
if crate::crosvm::sys::config::is_gpu_backend_deprecated(v) {
|
||||
return Err(argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from(
|
||||
"this backend type is deprecated, please use gfxstream.",
|
||||
),
|
||||
});
|
||||
} else {
|
||||
gpu_params.mode = GpuMode::Mode2D;
|
||||
}
|
||||
}
|
||||
"3d" | "3D" | "virglrenderer" => {
|
||||
if crate::crosvm::sys::config::is_gpu_backend_deprecated(v) {
|
||||
return Err(argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from(
|
||||
"this backend type is deprecated, please use gfxstream.",
|
||||
),
|
||||
});
|
||||
} else {
|
||||
gpu_params.mode = GpuMode::ModeVirglRenderer;
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "gfxstream")]
|
||||
"gfxstream" => {
|
||||
gpu_params.mode = GpuMode::ModeGfxstream;
|
||||
}
|
||||
_ => {
|
||||
return Err(argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from(
|
||||
#[cfg(feature = "gfxstream")]
|
||||
"gpu parameter 'backend' should be one of (2d|virglrenderer|gfxstream)",
|
||||
#[cfg(not(feature = "gfxstream"))]
|
||||
"gpu parameter 'backend' should be one of (2d|3d)",
|
||||
),
|
||||
});
|
||||
}
|
||||
},
|
||||
"egl" => match v {
|
||||
"true" | "" => {
|
||||
gpu_params.renderer_use_egl = true;
|
||||
}
|
||||
"false" => {
|
||||
gpu_params.renderer_use_egl = false;
|
||||
}
|
||||
_ => {
|
||||
return Err(argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from("gpu parameter 'egl' should be a boolean"),
|
||||
});
|
||||
}
|
||||
},
|
||||
"gles" => match v {
|
||||
"true" | "" => {
|
||||
gpu_params.renderer_use_gles = true;
|
||||
}
|
||||
"false" => {
|
||||
gpu_params.renderer_use_gles = false;
|
||||
}
|
||||
_ => {
|
||||
return Err(argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from("gpu parameter 'gles' should be a boolean"),
|
||||
});
|
||||
}
|
||||
},
|
||||
"glx" => match v {
|
||||
"true" | "" => {
|
||||
gpu_params.renderer_use_glx = true;
|
||||
}
|
||||
"false" => {
|
||||
gpu_params.renderer_use_glx = false;
|
||||
}
|
||||
_ => {
|
||||
return Err(argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from("gpu parameter 'glx' should be a boolean"),
|
||||
});
|
||||
}
|
||||
},
|
||||
"surfaceless" => match v {
|
||||
"true" | "" => {
|
||||
gpu_params.renderer_use_surfaceless = true;
|
||||
}
|
||||
"false" => {
|
||||
gpu_params.renderer_use_surfaceless = false;
|
||||
}
|
||||
_ => {
|
||||
return Err(argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from("gpu parameter 'surfaceless' should be a boolean"),
|
||||
});
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "gfxstream")]
|
||||
"syncfd" => {
|
||||
syncfd_specified = true;
|
||||
match v {
|
||||
"true" | "" => {
|
||||
gpu_params.gfxstream_use_syncfd = true;
|
||||
}
|
||||
"false" => {
|
||||
gpu_params.gfxstream_use_syncfd = false;
|
||||
}
|
||||
_ => {
|
||||
return Err(argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from("gpu parameter 'syncfd' should be a boolean"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "gfxstream")]
|
||||
"angle" => {
|
||||
angle_specified = true;
|
||||
match v {
|
||||
"true" | "" => {
|
||||
gpu_params.gfxstream_use_guest_angle = true;
|
||||
}
|
||||
"false" => {
|
||||
gpu_params.gfxstream_use_guest_angle = false;
|
||||
}
|
||||
_ => {
|
||||
return Err(argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from("gpu parameter 'angle' should be a boolean"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
"vulkan" => {
|
||||
#[cfg(feature = "gfxstream")]
|
||||
{
|
||||
vulkan_specified = true;
|
||||
}
|
||||
match v {
|
||||
"true" | "" => {
|
||||
gpu_params.use_vulkan = true;
|
||||
}
|
||||
"false" => {
|
||||
gpu_params.use_vulkan = false;
|
||||
}
|
||||
_ => {
|
||||
return Err(argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from("gpu parameter 'vulkan' should be a boolean"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "gfxstream")]
|
||||
"gles3.1" => {
|
||||
gles31_specified = true;
|
||||
match v {
|
||||
"true" | "" => {
|
||||
gpu_params.gfxstream_support_gles31 = true;
|
||||
}
|
||||
"false" => {
|
||||
gpu_params.gfxstream_support_gles31 = false;
|
||||
}
|
||||
_ => {
|
||||
return Err(argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from("gpu parameter 'gles3.1' should be a boolean"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
"wsi" => match v {
|
||||
"vk" => {
|
||||
gpu_params.wsi = Some(RutabagaWsi::Vulkan);
|
||||
}
|
||||
_ => {
|
||||
return Err(argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from("gpu parameter 'wsi' should be vk"),
|
||||
});
|
||||
}
|
||||
},
|
||||
"width" => {
|
||||
if let Some(width) = width {
|
||||
return Err(argument::Error::TooManyArguments(format!(
|
||||
"width was already specified: {}",
|
||||
width
|
||||
)));
|
||||
}
|
||||
width = Some(
|
||||
v.parse::<u32>()
|
||||
.map_err(|_| argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from("gpu parameter 'width' must be a valid integer"),
|
||||
})?,
|
||||
);
|
||||
}
|
||||
"height" => {
|
||||
if let Some(height) = height {
|
||||
return Err(argument::Error::TooManyArguments(format!(
|
||||
"height was already specified: {}",
|
||||
height
|
||||
)));
|
||||
}
|
||||
height = Some(
|
||||
v.parse::<u32>()
|
||||
.map_err(|_| argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from(
|
||||
"gpu parameter 'height' must be a valid integer",
|
||||
),
|
||||
})?,
|
||||
);
|
||||
}
|
||||
"dpi" => {
|
||||
if let Some(dpi) = dpi {
|
||||
return Err(argument::Error::TooManyArguments(format!(
|
||||
"dpi was already specified: {}",
|
||||
dpi
|
||||
)));
|
||||
}
|
||||
dpi = Some(
|
||||
v.parse::<u32>()
|
||||
.map_err(|_| argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from("gpu parameter 'dpi' must be a valid integer"),
|
||||
})?,
|
||||
);
|
||||
}
|
||||
#[cfg(feature = "gfxstream")]
|
||||
"refresh_rate" => {
|
||||
if let Some(vsync) = vsync {
|
||||
return Err(argument::Error::TooManyArguments(format!(
|
||||
"refresh_rate was already specified: {}",
|
||||
vsync
|
||||
)));
|
||||
}
|
||||
vsync = Some(
|
||||
v.parse::<u32>()
|
||||
.map_err(|_| argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from(
|
||||
"gpu parameter 'refresh_rate' must be a valid integer",
|
||||
),
|
||||
})?,
|
||||
);
|
||||
}
|
||||
"display_mode" => {
|
||||
if let Some(display_mode) = display_mode {
|
||||
return Err(argument::Error::TooManyArguments(format!(
|
||||
"display_mode was already specified: {}",
|
||||
display_mode
|
||||
)));
|
||||
}
|
||||
display_mode = Some(String::from(v));
|
||||
}
|
||||
"hidden" => match v {
|
||||
"true" | "" => {
|
||||
hidden = Some(true);
|
||||
}
|
||||
"false" => {
|
||||
hidden = Some(false);
|
||||
}
|
||||
_ => {
|
||||
return Err(argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from("gpu parameter 'hidden' should be a boolean"),
|
||||
});
|
||||
}
|
||||
},
|
||||
"cache-path" => gpu_params.cache_path = Some(v.to_string()),
|
||||
"cache-size" => gpu_params.cache_size = Some(v.to_string()),
|
||||
"udmabuf" => match v {
|
||||
"true" | "" => {
|
||||
gpu_params.udmabuf = true;
|
||||
}
|
||||
"false" => {
|
||||
gpu_params.udmabuf = false;
|
||||
}
|
||||
_ => {
|
||||
return Err(argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: String::from("gpu parameter 'udmabuf' should be a boolean"),
|
||||
});
|
||||
}
|
||||
},
|
||||
"context-types" => {
|
||||
let context_types: Vec<String> = v.split(':').map(|s| s.to_string()).collect();
|
||||
gpu_params.context_mask = calculate_context_mask(context_types);
|
||||
}
|
||||
"" => {}
|
||||
_ => {
|
||||
return Err(argument::Error::UnknownArgument(format!(
|
||||
"gpu parameter {}",
|
||||
k
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match display_mode.as_deref() {
|
||||
Some("windowed") => gpu_params.display_params = GpuDisplayParameters::default_windowed(),
|
||||
Some("borderless_full_screen") => gpu_params.display_params = GpuDisplayParameters::default_borderless_full_screen(),
|
||||
None => {}
|
||||
Some(display_mode) => return Err(argument::Error::InvalidValue {
|
||||
value: display_mode.to_string(),
|
||||
expected: String::from("gpu parameter 'display_mode' must be either 'borderless_full_screen' or 'windowed'")
|
||||
})
|
||||
}
|
||||
|
||||
if let Some(hidden) = hidden {
|
||||
gpu_params.display_params.hidden = hidden;
|
||||
}
|
||||
|
||||
#[cfg(feature = "gfxstream")]
|
||||
{
|
||||
if let Some(vsync) = vsync {
|
||||
gpu_params.vsync = vsync;
|
||||
}
|
||||
}
|
||||
|
||||
match gpu_params.display_params.display_mode {
|
||||
GpuDisplayMode::Windowed {
|
||||
width: ref mut width_in_params,
|
||||
height: ref mut height_in_params,
|
||||
dpi: ref mut dpi_in_params,
|
||||
} => {
|
||||
if let Some(width) = width {
|
||||
*width_in_params = width;
|
||||
}
|
||||
if let Some(height) = height {
|
||||
*height_in_params = height;
|
||||
}
|
||||
if let Some(dpi) = dpi {
|
||||
*dpi_in_params = dpi;
|
||||
}
|
||||
}
|
||||
GpuDisplayMode::BorderlessFullScreen(_) => {
|
||||
if width.is_some() || height.is_some() || dpi.is_some() {
|
||||
return Err(argument::Error::UnknownArgument(
|
||||
"width, height, or dpi is only supported for windowed display mode".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "gfxstream")]
|
||||
{
|
||||
if !vulkan_specified && gpu_params.mode == GpuMode::ModeGfxstream {
|
||||
gpu_params.use_vulkan = crate::crosvm::sys::config::use_vulkan();
|
||||
}
|
||||
if syncfd_specified || angle_specified || gles31_specified {
|
||||
match gpu_params.mode {
|
||||
GpuMode::ModeGfxstream => {}
|
||||
_ => {
|
||||
return Err(argument::Error::UnknownArgument(
|
||||
"gpu parameters syncfd and gles3.1 are only supported for gfxstream backend"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(gpu_params)
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpu")]
|
||||
pub(crate) fn validate_gpu_config(cfg: &mut Config) -> Result<(), String> {
|
||||
if let Some(gpu_parameters) = cfg.gpu_parameters.as_ref() {
|
||||
let (width, height) = gpu_parameters.display_params.get_virtual_display_size();
|
||||
for virtio_multi_touch in cfg.virtio_multi_touch.iter_mut() {
|
||||
virtio_multi_touch.set_default_size(width, height);
|
||||
}
|
||||
for virtio_single_touch in cfg.virtio_single_touch.iter_mut() {
|
||||
virtio_single_touch.set_default_size(width, height);
|
||||
}
|
||||
|
||||
let dpi = gpu_parameters.display_params.get_dpi();
|
||||
info!("using dpi {} on the Android guest", dpi);
|
||||
cfg.params.push(format!("androidboot.lcd_density={}", dpi));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Each type of process should have its own type here. This affects both exit
|
||||
/// handling and sandboxing policy.
|
||||
///
|
||||
/// WARNING: do NOT change the values items in this enum. The enum value is used in our exit codes,
|
||||
/// and relied upon by metrics analysis. The max value for this enum is 0x1F = 31 as it is
|
||||
/// restricted to five bits per `crate::crosvm::sys::windows::exit::to_process_type_error`.
|
||||
#[derive(Clone, Copy, PartialEq, Debug, enumn::N)]
|
||||
#[repr(u8)]
|
||||
pub enum ProcessType {
|
||||
Block = 1,
|
||||
Main = 2,
|
||||
Metrics = 3,
|
||||
Net = 4,
|
||||
Slirp = 5,
|
||||
}
|
||||
|
||||
impl From<ProcessType> for EmulatorProcessType {
|
||||
fn from(process_type: ProcessType) -> Self {
|
||||
match process_type {
|
||||
ProcessType::Block => EmulatorProcessType::PROCESS_TYPE_BLOCK,
|
||||
ProcessType::Main => EmulatorProcessType::PROCESS_TYPE_MAIN,
|
||||
ProcessType::Metrics => EmulatorProcessType::PROCESS_TYPE_METRICS,
|
||||
ProcessType::Net => EmulatorProcessType::PROCESS_TYPE_NET,
|
||||
ProcessType::Slirp => EmulatorProcessType::PROCESS_TYPE_SLIRP,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum IrqChipKind {
|
||||
/// All interrupt controllers are emulated in the kernel.
|
||||
Kernel,
|
||||
/// APIC is emulated in the kernel. All other interrupt controllers are in userspace.
|
||||
Split,
|
||||
/// All interrupt controllers are emulated in userspace.
|
||||
Userspace,
|
||||
}
|
||||
|
||||
impl FromStr for IrqChipKind {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"kernel" => Ok(Self::Kernel),
|
||||
"split" => Ok(Self::Split),
|
||||
"userspace" => Ok(Self::Userspace),
|
||||
_ => Err("invalid irqchip kind: expected \"kernel\", \"split\", or \"userspace\""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hypervisor backend.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum HypervisorKind {
|
||||
#[cfg(feature = "gvm")]
|
||||
Gvm,
|
||||
#[cfg(feature = "haxm")]
|
||||
Haxm,
|
||||
#[cfg(feature = "haxm")]
|
||||
Ghaxm,
|
||||
#[cfg(feature = "whpx")]
|
||||
Whpx,
|
||||
}
|
||||
|
||||
impl FromStr for HypervisorKind {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
#[cfg(feature = "gvm")]
|
||||
"gvm" => Ok(HypervisorKind::Gvm),
|
||||
#[cfg(feature = "haxm")]
|
||||
"haxm" => Ok(HypervisorKind::Haxm),
|
||||
#[cfg(feature = "haxm")]
|
||||
"ghaxm" => Ok(HypervisorKind::Ghaxm),
|
||||
#[cfg(feature = "whpx")]
|
||||
"whpx" => Ok(HypervisorKind::Whpx),
|
||||
_ => Err("invalid hypervisor backend"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[cfg(feature = "gpu")]
|
||||
use crate::crosvm::sys::config::parse_gpu_options;
|
||||
#[cfg(feature = "gpu")]
|
||||
use devices::virtio::gpu::GpuDisplayMode;
|
||||
|
||||
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
|
||||
#[test]
|
||||
fn parse_gpu_options_gfxstream_with_syncfd_specified() {
|
||||
{
|
||||
let gpu_params: GpuParameters =
|
||||
parse_gpu_options("backend=gfxstream,syncfd=true").unwrap();
|
||||
|
||||
assert!(gpu_params.gfxstream_use_syncfd);
|
||||
}
|
||||
{
|
||||
let gpu_params: GpuParameters =
|
||||
parse_gpu_options("syncfd=true,backend=gfxstream").unwrap();
|
||||
assert!(gpu_params.gfxstream_use_syncfd);
|
||||
}
|
||||
{
|
||||
let gpu_params: GpuParameters =
|
||||
parse_gpu_options("backend=gfxstream,syncfd=false").unwrap();
|
||||
|
||||
assert!(!gpu_params.gfxstream_use_syncfd);
|
||||
}
|
||||
{
|
||||
let gpu_params: GpuParameters =
|
||||
parse_gpu_options("syncfd=false,backend=gfxstream").unwrap();
|
||||
assert!(!gpu_params.gfxstream_use_syncfd);
|
||||
}
|
||||
{
|
||||
assert!(parse_gpu_options("backend=gfxstream,syncfd=invalid_value").is_err());
|
||||
}
|
||||
{
|
||||
assert!(parse_gpu_options("syncfd=invalid_value,backend=gfxstream").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
|
||||
#[test]
|
||||
fn parse_gpu_options_not_gfxstream_with_syncfd_specified() {
|
||||
{
|
||||
assert!(parse_gpu_options("backend=virglrenderer,syncfd=true").is_err());
|
||||
}
|
||||
{
|
||||
assert!(parse_gpu_options("syncfd=true,backend=virglrenderer").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
|
||||
#[test]
|
||||
fn parse_gpu_options_gfxstream_with_wsi_specified() {
|
||||
{
|
||||
let gpu_params: GpuParameters = parse_gpu_options("backend=gfxstream,wsi=vk").unwrap();
|
||||
assert!(matches!(gpu_params.wsi, Some(RutabagaWsi::Vulkan)));
|
||||
}
|
||||
{
|
||||
let gpu_params: GpuParameters = parse_gpu_options("wsi=vk,backend=gfxstream").unwrap();
|
||||
assert!(matches!(gpu_params.wsi, Some(RutabagaWsi::Vulkan)));
|
||||
}
|
||||
{
|
||||
assert!(parse_gpu_options("backend=gfxstream,wsi=invalid_value").is_err());
|
||||
}
|
||||
{
|
||||
assert!(parse_gpu_options("wsi=invalid_value,backend=gfxstream").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "audio")]
|
||||
#[test]
|
||||
fn parse_ac97_vaild() {
|
||||
crate::crosvm::config::parse_ac97_options("backend=win_audio")
|
||||
.expect("parse should have succeded");
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "gpu"))]
|
||||
#[test]
|
||||
fn parse_gpu_options_default_vulkan_support() {
|
||||
#[cfg(unix)]
|
||||
assert!(
|
||||
!parse_gpu_options("backend=virglrenderer")
|
||||
.unwrap()
|
||||
.use_vulkan
|
||||
);
|
||||
#[cfg(feature = "gfxstream")]
|
||||
assert!(!parse_gpu_options("backend=gfxstream").unwrap().use_vulkan);
|
||||
#[cfg(all(feature = "gfxstream", unix))]
|
||||
assert!(parse_gpu_options("backend=gfxstream").unwrap().use_vulkan);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "gpu"))]
|
||||
#[test]
|
||||
fn parse_gpu_options_with_vulkan_specified() {
|
||||
assert!(parse_gpu_options("vulkan=true").unwrap().use_vulkan);
|
||||
#[cfg(unix)]
|
||||
assert!(
|
||||
parse_gpu_options("backend=virglrenderer,vulkan=true")
|
||||
.unwrap()
|
||||
.use_vulkan
|
||||
);
|
||||
#[cfg(unix)]
|
||||
assert!(
|
||||
parse_gpu_options("vulkan=true,backend=virglrenderer")
|
||||
.unwrap()
|
||||
.use_vulkan
|
||||
);
|
||||
assert!(!parse_gpu_options("vulkan=false").unwrap().use_vulkan);
|
||||
#[cfg(unix)]
|
||||
assert!(
|
||||
!parse_gpu_options("backend=virglrenderer,vulkan=false")
|
||||
.unwrap()
|
||||
.use_vulkan
|
||||
);
|
||||
#[cfg(unix)]
|
||||
assert!(
|
||||
!parse_gpu_options("vulkan=false,backend=virglrenderer")
|
||||
.unwrap()
|
||||
.use_vulkan
|
||||
);
|
||||
#[cfg(unix)]
|
||||
assert!(parse_gpu_options("backend=virglrenderer,vulkan=invalid_value").is_err());
|
||||
assert!(parse_gpu_options("vulkan=invalid_value,backend=virglrenderer").is_err());
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
|
||||
#[test]
|
||||
fn parse_gpu_options_gfxstream_with_gles31_specified() {
|
||||
assert!(
|
||||
parse_gpu_options("backend=gfxstream,gles3.1=true")
|
||||
.unwrap()
|
||||
.gfxstream_support_gles31
|
||||
);
|
||||
assert!(
|
||||
parse_gpu_options("gles3.1=true,backend=gfxstream")
|
||||
.unwrap()
|
||||
.gfxstream_support_gles31
|
||||
);
|
||||
assert!(
|
||||
!parse_gpu_options("backend=gfxstream,gles3.1=false")
|
||||
.unwrap()
|
||||
.gfxstream_support_gles31
|
||||
);
|
||||
assert!(
|
||||
!parse_gpu_options("gles3.1=false,backend=gfxstream")
|
||||
.unwrap()
|
||||
.gfxstream_support_gles31
|
||||
);
|
||||
assert!(parse_gpu_options("backend=gfxstream,gles3.1=invalid_value").is_err());
|
||||
assert!(parse_gpu_options("gles3.1=invalid_value,backend=gfxstream").is_err());
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
|
||||
#[test]
|
||||
fn parse_gpu_options_not_gfxstream_with_gles31_specified() {
|
||||
assert!(parse_gpu_options("backend=virglrenderer,gles3.1=true").is_err());
|
||||
assert!(parse_gpu_options("gles3.1=true,backend=virglrenderer").is_err());
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpu")]
|
||||
#[test]
|
||||
fn parse_gpu_options_gpu_display_mode() {
|
||||
let display_params = parse_gpu_options("display_mode=windowed")
|
||||
.unwrap()
|
||||
.display_params;
|
||||
assert!(matches!(
|
||||
display_params.display_mode,
|
||||
GpuDisplayMode::Windowed { .. }
|
||||
));
|
||||
|
||||
let display_params = parse_gpu_options("display_mode=borderless_full_screen")
|
||||
.unwrap()
|
||||
.display_params;
|
||||
assert!(matches!(
|
||||
display_params.display_mode,
|
||||
GpuDisplayMode::BorderlessFullScreen(_)
|
||||
));
|
||||
|
||||
assert!(parse_gpu_options("display_mode=invalid_mode").is_err());
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpu")]
|
||||
#[test]
|
||||
fn parse_gpu_options_gpu_display_mode_duplicated() {
|
||||
assert!(parse_gpu_options("display_mode=windowed,display_mode=windowed").is_err());
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpu")]
|
||||
#[test]
|
||||
fn parse_gpu_options_borderless_full_screen_shouldnt_be_specified_with_size() {
|
||||
assert!(parse_gpu_options("display_mode=borderless_full_screen,width=1280").is_err());
|
||||
assert!(parse_gpu_options("display_mode=borderless_full_screen,height=720").is_err());
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpu")]
|
||||
#[test]
|
||||
fn parse_gpu_options_windowed_with_size() {
|
||||
const WIDTH: u32 = 1720;
|
||||
const HEIGHT: u32 = 1800;
|
||||
const DPI: u32 = 1808;
|
||||
|
||||
let display_params =
|
||||
parse_gpu_options(format!("display_mode=windowed,width={}", WIDTH).as_str())
|
||||
.unwrap()
|
||||
.display_params;
|
||||
assert!(
|
||||
matches!(display_params.display_mode, GpuDisplayMode::Windowed { width, .. } if width == WIDTH)
|
||||
);
|
||||
|
||||
let display_params =
|
||||
parse_gpu_options(format!("display_mode=windowed,height={}", HEIGHT).as_str())
|
||||
.unwrap()
|
||||
.display_params;
|
||||
assert!(
|
||||
matches!(display_params.display_mode, GpuDisplayMode::Windowed { height, .. } if height == HEIGHT)
|
||||
);
|
||||
|
||||
let display_params =
|
||||
parse_gpu_options(format!("display_mode=windowed,dpi={}", DPI).as_str())
|
||||
.unwrap()
|
||||
.display_params;
|
||||
assert!(
|
||||
matches!(display_params.display_mode, GpuDisplayMode::Windowed { dpi, .. } if dpi == DPI)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpu")]
|
||||
#[test]
|
||||
fn parse_gpu_options_hidden() {
|
||||
let display_params = parse_gpu_options(format!("hidden=true").as_str())
|
||||
.unwrap()
|
||||
.display_params;
|
||||
assert!(display_params.hidden);
|
||||
|
||||
let display_params = parse_gpu_options(format!("hidden=false").as_str())
|
||||
.unwrap()
|
||||
.display_params;
|
||||
assert!(matches!(display_params.hidden, false));
|
||||
}
|
||||
|
||||
#[cfg(feature = "gpu")]
|
||||
#[test]
|
||||
fn parse_gpu_options_size_duplicated() {
|
||||
assert!(parse_gpu_options("width=1280,width=1280").is_err());
|
||||
assert!(parse_gpu_options("height=1280,height=1280").is_err());
|
||||
assert!(parse_gpu_options("dpi=1280,dpi=1280").is_err());
|
||||
}
|
||||
}
|
489
src/crosvm/sys/windows/exit.rs
Normal file
489
src/crosvm/sys/windows/exit.rs
Normal file
|
@ -0,0 +1,489 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! Enum and Anyhow helpers to set the process exit code.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use crate::crosvm::sys::config::ProcessType;
|
||||
use anyhow::Context;
|
||||
|
||||
pub type ExitCode = i32;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExitCodeWrapper(pub ExitCode);
|
||||
|
||||
impl Display for ExitCodeWrapper {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "exit code: {} = 0x{:08x}", self.0, self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for attaching context with process exit codes to a std::result::Result.
|
||||
pub trait ExitContext<T, E> {
|
||||
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
|
||||
where
|
||||
X: Into<ExitCode>;
|
||||
|
||||
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
|
||||
where
|
||||
X: Into<ExitCode>,
|
||||
C: Display + Send + Sync + 'static;
|
||||
|
||||
fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
|
||||
where
|
||||
X: Into<ExitCode>,
|
||||
C: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> C;
|
||||
}
|
||||
|
||||
impl<T, E> ExitContext<T, E> for std::result::Result<T, E>
|
||||
where
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
|
||||
where
|
||||
X: Into<ExitCode>,
|
||||
{
|
||||
self.context(ExitCodeWrapper(exit_code.into()))
|
||||
}
|
||||
|
||||
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
|
||||
where
|
||||
X: Into<ExitCode>,
|
||||
C: Display + Send + Sync + 'static,
|
||||
{
|
||||
self.context(ExitCodeWrapper(exit_code.into()))
|
||||
.context(context)
|
||||
}
|
||||
|
||||
fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
|
||||
where
|
||||
X: Into<ExitCode>,
|
||||
C: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> C,
|
||||
{
|
||||
self.context(ExitCodeWrapper(exit_code.into()))
|
||||
.with_context(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for attaching context with process exit codes to an anyhow::Result.
|
||||
pub trait ExitContextAnyhow<T> {
|
||||
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
|
||||
where
|
||||
X: Into<ExitCode>;
|
||||
|
||||
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
|
||||
where
|
||||
X: Into<ExitCode>,
|
||||
C: Display + Send + Sync + 'static;
|
||||
|
||||
fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
|
||||
where
|
||||
X: Into<ExitCode>,
|
||||
C: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> C;
|
||||
|
||||
fn to_exit_code(&self) -> Option<ExitCode>;
|
||||
}
|
||||
|
||||
impl<T> ExitContextAnyhow<T> for anyhow::Result<T> {
|
||||
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
|
||||
where
|
||||
X: Into<ExitCode>,
|
||||
{
|
||||
self.context(ExitCodeWrapper(exit_code.into()))
|
||||
}
|
||||
|
||||
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
|
||||
where
|
||||
X: Into<ExitCode>,
|
||||
C: Display + Send + Sync + 'static,
|
||||
{
|
||||
self.context(ExitCodeWrapper(exit_code.into()))
|
||||
.context(context)
|
||||
}
|
||||
|
||||
fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
|
||||
where
|
||||
X: Into<ExitCode>,
|
||||
C: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> C,
|
||||
{
|
||||
self.context(ExitCodeWrapper(exit_code.into()))
|
||||
.with_context(f)
|
||||
}
|
||||
|
||||
fn to_exit_code(&self) -> Option<ExitCode> {
|
||||
self.as_ref()
|
||||
.err()
|
||||
.map(|e| e.downcast_ref::<ExitCodeWrapper>())
|
||||
.flatten()
|
||||
.map(|w| w.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bail_exit_code {
|
||||
($exit_code:literal, $msg:literal $(,)?) => {
|
||||
return Err(anyhow!($msg)).exit_code($exit_code)
|
||||
};
|
||||
($exit_code:literal, $err:expr $(,)?) => {
|
||||
return Err(anyhow!($err)).exit_code($exit_code)
|
||||
};
|
||||
($exit_code:literal, $fmt:expr, $($arg:tt)*) => {
|
||||
return Err(anyhow!($fmt, $($arg)*)).exit_code($exit_code)
|
||||
};
|
||||
($exit_code:expr, $msg:literal $(,)?) => {
|
||||
return Err(anyhow!($msg)).exit_code($exit_code)
|
||||
};
|
||||
($exit_code:expr, $err:expr $(,)?) => {
|
||||
return Err(anyhow!($err)).exit_code($exit_code)
|
||||
};
|
||||
($exit_code:expr, $fmt:expr, $($arg:tt)*) => {
|
||||
return Err(anyhow!($fmt, $($arg)*)).exit_code($exit_code)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ensure_exit_code {
|
||||
($cond:expr, $exit_code:literal $(,)?) => {
|
||||
if !$cond {
|
||||
bail_exit_code!($exit_code, concat!("Condition failed: `", stringify!($cond), "`"));
|
||||
}
|
||||
};
|
||||
($cond:expr, $exit_code:literal, $msg:literal $(,)?) => {
|
||||
if !$cond {
|
||||
bail_exit_code!($exit_code, $msg);
|
||||
}
|
||||
};
|
||||
($cond:expr, $exit_code:literal, $err:expr $(,)?) => {
|
||||
if !$cond {
|
||||
bail_exit_code!($exit_code, $err);
|
||||
}
|
||||
};
|
||||
($cond:expr, $exit_code:literal, $fmt:expr, $($arg:tt)*) => {
|
||||
if !$cond {
|
||||
bail_exit_code!($exit_code, $fmt, $($arg)*);
|
||||
}
|
||||
};
|
||||
($cond:expr, $exit_code:expr $(,)?) => {
|
||||
if !$cond {
|
||||
bail_exit_code!($exit_code, concat!("Condition failed: `", stringify!($cond), "`"));
|
||||
}
|
||||
};
|
||||
($cond:expr, $exit_code:expr, $msg:literal $(,)?) => {
|
||||
if !$cond {
|
||||
bail_exit_code!($exit_code, $msg);
|
||||
}
|
||||
};
|
||||
($cond:expr, $exit_code:expr, $err:expr $(,)?) => {
|
||||
if !$cond {
|
||||
bail_exit_code!($exit_code, $err);
|
||||
}
|
||||
};
|
||||
($cond:expr, $exit_code:expr, $fmt:expr, $($arg:tt)*) => {
|
||||
if !$cond {
|
||||
bail_exit_code!($exit_code, $fmt, $($arg)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum Exit {
|
||||
// Windows process exit codes triggered by the kernel tend to be NTSTATUS, so we treat
|
||||
// our error codes as NTSTATUS to avoid clashing. This means we set the vendor bit. We also
|
||||
// set the severity to error. As these all set in the MSB, we can write this as a prefix of
|
||||
// 0xE0.
|
||||
//
|
||||
// Because of how these error codes are used in CommandType, we can only use the lower two
|
||||
// bytes of the u32 for our error codes; in other words, the legal range is
|
||||
// [0xE0000000, 0xE000FFFF].
|
||||
AddGpuDeviceMemory = 0xE0000001,
|
||||
AddIrqChipVcpu = 0xE0000002,
|
||||
AddPmemDeviceMemory = 0xE0000003,
|
||||
AllocateGpuDeviceAddress = 0xE0000004,
|
||||
AllocatePmemDeviceAddress = 0xE0000005,
|
||||
BlockDeviceNew = 0xE0000006,
|
||||
BuildVm = 0xE0000007,
|
||||
ChownTpmStorage = 0xE0000008,
|
||||
CloneEvent = 0xE000000A,
|
||||
CloneVcpu = 0xE000000B,
|
||||
ConfigureVcpu = 0xE000000C,
|
||||
CreateAc97 = 0xE000000D,
|
||||
CreateConsole = 0xE000000E,
|
||||
CreateDisk = 0xE000000F,
|
||||
CreateEvent = 0xE0000010,
|
||||
CreateGralloc = 0xE0000011,
|
||||
CreateGvm = 0xE0000012,
|
||||
CreateSocket = 0xE0000013,
|
||||
CreateTapDevice = 0xE0000014,
|
||||
CreateTimer = 0xE0000015,
|
||||
CreateTpmStorage = 0xE0000016,
|
||||
CreateVcpu = 0xE0000017,
|
||||
CreateWaitContext = 0xE0000018,
|
||||
Disk = 0xE0000019,
|
||||
DiskImageLock = 0xE000001A,
|
||||
DropCapabilities = 0xE000001B,
|
||||
EventDeviceSetup = 0xE000001C,
|
||||
EnableHighResTimer = 0xE000001D,
|
||||
HandleCreateQcowError = 0xE000001E,
|
||||
HandleVmRequestError = 0xE0000020,
|
||||
InitSysLogError = 0xE0000021,
|
||||
InputDeviceNew = 0xE0000022,
|
||||
InputEventsOpen = 0xE0000023,
|
||||
InvalidRunArgs = 0xE0000025,
|
||||
InvalidSubCommand = 0xE0000026,
|
||||
InvalidSubCommandArgs = 0xE0000027,
|
||||
InvalidWaylandPath = 0xE0000028,
|
||||
LoadKernel = 0xE0000029,
|
||||
MissingCommandArg = 0xE0000030,
|
||||
ModifyBatteryError = 0xE0000031,
|
||||
NetDeviceNew = 0xE0000032,
|
||||
OpenAcpiTable = 0xE0000033,
|
||||
OpenAndroidFstab = 0xE0000034,
|
||||
OpenBios = 0xE0000035,
|
||||
OpenInitrd = 0xE0000036,
|
||||
OpenKernel = 0xE0000037,
|
||||
OpenVinput = 0xE0000038,
|
||||
PivotRootDoesntExist = 0xE0000039,
|
||||
PmemDeviceImageTooBig = 0xE000003A,
|
||||
PmemDeviceNew = 0xE000003B,
|
||||
ReadMemAvailable = 0xE000003C,
|
||||
RegisterBalloon = 0xE000003D,
|
||||
RegisterBlock = 0xE000003E,
|
||||
RegisterGpu = 0xE000003F,
|
||||
RegisterNet = 0xE0000040,
|
||||
RegisterP9 = 0xE0000041,
|
||||
RegisterRng = 0xE0000042,
|
||||
RegisterWayland = 0xE0000043,
|
||||
ReserveGpuMemory = 0xE0000044,
|
||||
ReserveMemory = 0xE0000045,
|
||||
ReservePmemMemory = 0xE0000046,
|
||||
ResetTimer = 0xE0000047,
|
||||
RngDeviceNew = 0xE0000048,
|
||||
RunnableVcpu = 0xE0000049,
|
||||
SettingSignalMask = 0xE000004B,
|
||||
SpawnVcpu = 0xE000004D,
|
||||
SysUtil = 0xE000004E,
|
||||
Timer = 0xE000004F,
|
||||
ValidateRawDescriptor = 0xE0000050,
|
||||
VirtioPciDev = 0xE0000051,
|
||||
WaitContextAdd = 0xE0000052,
|
||||
WaitContextDelete = 0xE0000053,
|
||||
WhpxSetupError = 0xE0000054,
|
||||
VcpuFailEntry = 0xE0000055,
|
||||
VcpuRunError = 0xE0000056,
|
||||
VcpuShutdown = 0xE0000057,
|
||||
VcpuSystemEvent = 0xE0000058,
|
||||
WaitUntilRunnable = 0xE0000059,
|
||||
CreateControlServer = 0xE000005A,
|
||||
CreateTube = 0xE000005B,
|
||||
UsbError = 0xE000005E,
|
||||
GuestMemoryLayout = 0xE000005F,
|
||||
CreateVm = 0xE0000060,
|
||||
CreateGuestMemory = 0xE0000061,
|
||||
CreateIrqChip = 0xE0000062,
|
||||
SpawnIrqThread = 0xE0000063,
|
||||
ConnectTube = 0xE0000064,
|
||||
BalloonDeviceNew = 0xE0000065,
|
||||
BalloonStats = 0xE0000066,
|
||||
BorrowVfioContainer = 0xE0000067,
|
||||
OpenCompositeFooterFile = 0xE0000068,
|
||||
OpenCompositeHeaderFile = 0xE0000069,
|
||||
OpenCompositeImageFile = 0xE0000070,
|
||||
CreateCompositeDisk = 0xE0000071,
|
||||
MissingControlTube = 0xE0000072,
|
||||
TubeTransporterInit = 0xE0000073,
|
||||
TubeFailure = 0xE0000074,
|
||||
ProcessSpawnFailed = 0xE0000075,
|
||||
LogFile = 0xE0000076,
|
||||
CreateZeroFiller = 0xE0000077,
|
||||
GenerateAcpi = 0xE0000078,
|
||||
WaitContextWait = 0xE0000079,
|
||||
SetSigintHandler = 0xE000007A,
|
||||
KilledBySignal = 0xE000007B,
|
||||
BrokerDeviceExitedTimeout = 0xE000007C,
|
||||
BrokerMainExitedTimeout = 0xE000007D,
|
||||
MemoryTooLarge = 0xE000007E,
|
||||
BrokerMetricsExitedTimeout = 0xE000007F,
|
||||
MetricsController = 0xE0000080,
|
||||
SwiotlbTooLarge = 0xE0000081,
|
||||
UserspaceVsockDeviceNew = 0xE0000082,
|
||||
VhostUserBlockDeviceNew = 0xE0000083,
|
||||
CrashReportingInit = 0xE0000084,
|
||||
StartBackendDevice = 0xE0000085,
|
||||
ConfigureHotPlugDevice = 0xE0000086,
|
||||
InvalidHotPlugKey = 0xE0000087,
|
||||
InvalidVfioPath = 0xE0000088,
|
||||
NoHotPlugBus = 0xE0000089,
|
||||
SandboxError = 0xE000008A,
|
||||
Pstore = 0xE000008B,
|
||||
ProcessInvariantsInit = 0xE000008C,
|
||||
VirtioVhostUserDeviceNew = 0xE000008D,
|
||||
CloneTube = 0xE000008E,
|
||||
VhostUserGpuDeviceNew = 0xE000008F,
|
||||
CreateAsyncDisk = 0xE0000090,
|
||||
CreateDiskCheckAsyncOkError = 0xE0000091,
|
||||
VhostUserNetDeviceNew = 0xE0000092,
|
||||
BrokerSigtermTimeout = 0xE0000093,
|
||||
SpawnVcpuMonitor = 0xE0000094,
|
||||
NoDefaultHypervisor = 0xE0000095,
|
||||
TscCalibrationFailed = 0xE0000096,
|
||||
UnknownError = 0xE0000097,
|
||||
CommonChildSetupError = 0xE0000098,
|
||||
}
|
||||
|
||||
impl From<Exit> for ExitCode {
|
||||
fn from(exit: Exit) -> Self {
|
||||
exit as ExitCode
|
||||
}
|
||||
}
|
||||
|
||||
// Bitfield masks for NTSTATUS & our extension of the format. See to_process_type_error for details.
|
||||
mod bitmasks {
|
||||
pub const FACILITY_FIELD_LOWER_MASK: u32 = u32::from_be_bytes([0x00, 0x3F, 0x00, 0x00]);
|
||||
pub const EXTRA_DATA_FIELD_MASK: u32 = u32::from_be_bytes([0x0F, 0xC0, 0x00, 0x00]);
|
||||
#[cfg(test)]
|
||||
pub const EXTRA_DATA_FIELD_COMMAND_TYPE_MASK: u32 =
|
||||
u32::from_be_bytes([0x07, 0xC0, 0x00, 0x00]);
|
||||
pub const EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK: u32 =
|
||||
u32::from_be_bytes([0x08, 0x00, 0x00, 0x00]);
|
||||
pub const VENDOR_FIELD_MASK: u32 = u32::from_be_bytes([0x20, 0x00, 0x00, 0x00]);
|
||||
pub const RESERVED_BIT_MASK: u32 = u32::from_be_bytes([0x10, 0x00, 0x00, 0x00]);
|
||||
pub const COMMAND_TYPE_MASK: u32 = u32::from_be_bytes([0x00, 0x00, 0x00, 0x1F]);
|
||||
}
|
||||
use bitmasks::*;
|
||||
|
||||
/// If you are looking for a fun interview question, you have come to the right place. To
|
||||
/// understand the details of NTSTATUS, which you'll want to do before reading further, visit
|
||||
/// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/87fba13e-bf06-450e-83b1-9241dc81e781.
|
||||
///
|
||||
/// This function is unfortunately what happens when you only have six bits to store auxiliary
|
||||
/// information, and have to fit in with an existing bitfield's schema.
|
||||
///
|
||||
/// This function packs bits in NTSTATUS results (generally what a Windows exit code should be).
|
||||
/// There are three primary cases it deals with:
|
||||
/// * Vendor specific exits. These are error codes we generate explicitly in crosvm. We will
|
||||
/// pack these codes with the lower 6 "facility" bits set so they can't collide with the other
|
||||
/// cases. The MSB of the facility field will be clear.
|
||||
///
|
||||
/// * Non vendor NTSTATUS exits. These are error codes which come from Windows. We flip the
|
||||
/// vendor bit on these because we're going to pack the facility field, and leaving it unset
|
||||
/// would cause us to violate the rule that if the vendor bit is unset, we shouldn't exceed
|
||||
/// FACILITY_MAXIMUM_VALUE in that field. The MSB of the facility field will be clear.
|
||||
///
|
||||
/// * Non NTSTATUS errors. We detect these with two heuristics:
|
||||
/// a) Reserved field is set.
|
||||
/// b) The facility field has exceeded the bottom six bits.
|
||||
///
|
||||
/// For such cases, we pack as much of the error as we can into the lower 6 bits of the
|
||||
/// facility field, and code field (2 bytes). In this case, the most significant bit of the
|
||||
/// facility field is set.
|
||||
///
|
||||
/// For all of the cases above, we pack the most significant 5 bits of the facility field with
|
||||
/// information about what command type generated this error.
|
||||
pub fn to_process_type_error(error_code: u32, cmd_type: ProcessType) -> u32 {
|
||||
let is_vendor = error_code & VENDOR_FIELD_MASK != 0;
|
||||
|
||||
// The reserved bit is always clear on a NTSTATUS code.
|
||||
let is_reserved_bit_clear = error_code & RESERVED_BIT_MASK == 0;
|
||||
|
||||
// The six most significant bits of the facility field are where we'll be storing our
|
||||
// command type (and whether we have a valid NTSTATUS error). If bits are already set there,
|
||||
// it means this isn't a valid NTSTATUS code.
|
||||
let is_extra_data_field_clear = error_code & EXTRA_DATA_FIELD_MASK == 0;
|
||||
|
||||
let is_ntstatus = is_reserved_bit_clear && is_extra_data_field_clear;
|
||||
|
||||
// We use the top bit of the facility field to store whether we ran out of space to pack
|
||||
// the error. The next five bits are where we store the command type, so we'll shift them
|
||||
// into the appropriate position here.
|
||||
let command_type = (cmd_type as u32 & COMMAND_TYPE_MASK) << 22;
|
||||
|
||||
match (is_ntstatus, is_vendor) {
|
||||
// Valid vendor code
|
||||
(true, true) => {
|
||||
// Set all the lower facility bits, and attach the command type.
|
||||
error_code | FACILITY_FIELD_LOWER_MASK | command_type
|
||||
}
|
||||
|
||||
// Valid non-vendor code
|
||||
(true, false) => {
|
||||
// Set the vendor bit and attach the command type.
|
||||
error_code | VENDOR_FIELD_MASK | command_type
|
||||
}
|
||||
|
||||
// Not a valid NTSTATUS code.
|
||||
_ => {
|
||||
// Clear the extra data field, and set the the top bit of the facility field to
|
||||
// signal that we didn't have enough space for the full error codes.
|
||||
error_code & !EXTRA_DATA_FIELD_MASK | command_type | EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use winapi::shared::ntstatus::STATUS_BAD_INITIAL_PC;
|
||||
|
||||
#[test]
|
||||
fn test_to_process_type_error_ntstatus_vendor() {
|
||||
let e = to_process_type_error(Exit::InvalidRunArgs as u32, ProcessType::Main);
|
||||
assert_eq!(
|
||||
e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
|
||||
(ProcessType::Main as u32) << 22
|
||||
);
|
||||
assert_eq!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
|
||||
|
||||
// This is a valid NTSTATUS error.
|
||||
assert_eq!(e & RESERVED_BIT_MASK, 0);
|
||||
|
||||
// Check the actual crosvm error code contained in the NTSTATUS. We don't mutate the
|
||||
// severity field, so we don't mask it off. We mask off the facility field entirely because
|
||||
// that's where we stored the command type & NTSTATUS validity bit.
|
||||
assert_eq!(e & 0xF000FFFF_u32, Exit::InvalidRunArgs as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_process_type_error_ntstatus_non_vendor() {
|
||||
let e = to_process_type_error(STATUS_BAD_INITIAL_PC as u32, ProcessType::Main);
|
||||
assert_eq!(
|
||||
e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
|
||||
(ProcessType::Main as u32) << 22
|
||||
);
|
||||
assert_eq!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
|
||||
|
||||
// This is a valid NTSTATUS error.
|
||||
assert_eq!(e & RESERVED_BIT_MASK, 0);
|
||||
|
||||
// Check the actual error code contained in the NTSTATUS. We mask off all our extra data
|
||||
// fields and switch off the vendor bit to confirm the actual code was left alone.
|
||||
assert_eq!(
|
||||
e & !EXTRA_DATA_FIELD_MASK & !VENDOR_FIELD_MASK,
|
||||
STATUS_BAD_INITIAL_PC as u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_process_type_error_wontfit_ntstatus() {
|
||||
let e = to_process_type_error(0xFFFFFFFF, ProcessType::Main);
|
||||
assert_eq!(
|
||||
e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
|
||||
(ProcessType::Main as u32) << 22
|
||||
);
|
||||
|
||||
// -1 is not a valid NTSTATUS error.
|
||||
assert_ne!(e & RESERVED_BIT_MASK, 0);
|
||||
|
||||
// Overflow did occur.
|
||||
assert_ne!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
|
||||
|
||||
// Check that we left the rest of the bits (except for our command type field & overflow
|
||||
// bit) in the exit code untouched.
|
||||
assert_eq!(e & 0xF03FFFFF_u32, 0xF03FFFFF_u32);
|
||||
}
|
||||
}
|
241
src/crosvm/sys/windows/stats.rs
Normal file
241
src/crosvm/sys/windows/stats.rs
Normal file
|
@ -0,0 +1,241 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::cmp::Reverse;
|
||||
use std::fmt;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use devices::BusStatistics;
|
||||
use hypervisor::VcpuExit;
|
||||
|
||||
const ERROR_RETRY_I32: i32 = winapi::shared::winerror::ERROR_RETRY as i32;
|
||||
|
||||
/// Statistics about the number and duration of VM exits.
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub struct VmExitStatistics {
|
||||
/// Whether or not statistics have been enabled to measure VM exits.
|
||||
enabled: bool,
|
||||
/// Counter of the number of VM exits per-exit-type. The index into the Vec can be determined
|
||||
/// from a &Result<VcpuExit> via the `exit_to_index` function.
|
||||
exit_counters: Vec<u64>,
|
||||
/// Sum of the duration of VM exits per-exit-type. The index into the Vec can be determined
|
||||
/// from a &Result<VcpuExit> via the `exit_to_index` function.
|
||||
exit_durations: Vec<Duration>,
|
||||
}
|
||||
|
||||
impl VmExitStatistics {
|
||||
pub fn new() -> VmExitStatistics {
|
||||
VmExitStatistics {
|
||||
enabled: false,
|
||||
// We have a known number of exit types, and thus a known number of exit indices
|
||||
exit_counters: vec![0; MAX_EXIT_INT + 1],
|
||||
exit_durations: vec![Duration::new(0, 0); MAX_EXIT_INT + 1],
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable or disable statistics gathering.
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
|
||||
/// Get the start time of the stat that is to be recorded.
|
||||
///
|
||||
/// If the VmExitStatistics instance is not enabled this will return None.
|
||||
pub fn start_stat(&self) -> Option<Instant> {
|
||||
if !self.enabled {
|
||||
return None;
|
||||
}
|
||||
Some(Instant::now())
|
||||
}
|
||||
|
||||
/// Record the end of the stat.
|
||||
///
|
||||
/// The start value return from start_stat should be passed as `start`. If `start` is None or
|
||||
/// if the VmExitStatistics instance is not enabled this will do nothing. The counters and
|
||||
/// durations will silently overflow to prevent interference with vm operation.
|
||||
pub fn end_stat(&mut self, exit: &base::Result<VcpuExit>, start: Option<Instant>) {
|
||||
if !self.enabled || start.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let exit_index = exit_to_index(exit);
|
||||
|
||||
// We overflow because we don't want any disruptions to emulator running due to
|
||||
// statistics
|
||||
self.exit_counters[exit_index] = self.exit_counters[exit_index].overflowing_add(1).0;
|
||||
self.exit_durations[exit_index] = self.exit_durations[exit_index]
|
||||
.checked_add(start.unwrap().elapsed())
|
||||
.unwrap_or(Duration::new(0, 0)); // If we overflow, reset to 0
|
||||
}
|
||||
|
||||
/// Merge several VmExitStatistics into one.
|
||||
pub fn merged(stats: &[VmExitStatistics]) -> VmExitStatistics {
|
||||
let mut merged = VmExitStatistics::new();
|
||||
for other in stats.iter() {
|
||||
for exit_index in 0..(MAX_EXIT_INT + 1) {
|
||||
// We overflow because we don't want any disruptions to emulator running due to
|
||||
// statistics
|
||||
merged.exit_counters[exit_index] = merged.exit_counters[exit_index]
|
||||
.overflowing_add(other.exit_counters[exit_index])
|
||||
.0;
|
||||
merged.exit_durations[exit_index] = merged.exit_durations[exit_index]
|
||||
.checked_add(other.exit_durations[exit_index])
|
||||
.unwrap_or(Duration::new(0, 0)); // If we overflow, reset to 0
|
||||
}
|
||||
}
|
||||
|
||||
merged
|
||||
}
|
||||
|
||||
/// Get a json representation of `self`. Returns an array of maps, where each map contains the
|
||||
/// count and duration of a particular vmexit.
|
||||
pub fn json(&self) -> serde_json::Value {
|
||||
let mut exits = serde_json::json!([]);
|
||||
let exits_vec = exits.as_array_mut().unwrap();
|
||||
for exit_index in 0..(MAX_EXIT_INT + 1) {
|
||||
exits_vec.push(serde_json::json!({
|
||||
"exit_type": exit_index_to_str(exit_index),
|
||||
"count": self.exit_counters[exit_index],
|
||||
"duration": {
|
||||
"seconds": self.exit_durations[exit_index].as_secs(),
|
||||
"subsecond_nanos": self.exit_durations[exit_index].subsec_nanos(),
|
||||
}
|
||||
}))
|
||||
}
|
||||
exits
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VmExitStatistics {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "Exit Type Count Duration")?;
|
||||
|
||||
let mut exit_indices: Vec<usize> = (0..(MAX_EXIT_INT + 1)).collect();
|
||||
// Sort exit indices by exit_duration
|
||||
exit_indices.sort_by_key(|i| Reverse(self.exit_durations[*i]));
|
||||
|
||||
for exit_index in exit_indices {
|
||||
writeln!(
|
||||
f,
|
||||
"{:<16}{:<16}{:<16}",
|
||||
exit_index_to_str(exit_index),
|
||||
self.exit_counters[exit_index],
|
||||
// Alignment not implemented by Debug
|
||||
format!("{:?}", self.exit_durations[exit_index]),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This constant should be set to the maximum integer to which the below functions will map a
|
||||
/// VcpuExit.
|
||||
const MAX_EXIT_INT: usize = 13;
|
||||
|
||||
/// Map Vm Exits to exit indexes, which are integers for storage in our counter Vecs.
|
||||
fn exit_to_index(exit: &base::Result<VcpuExit>) -> usize {
|
||||
match exit {
|
||||
Ok(VcpuExit::Io { .. }) => 0,
|
||||
Ok(VcpuExit::Mmio { .. }) => 1,
|
||||
Ok(VcpuExit::IoapicEoi { .. }) => 2,
|
||||
Ok(VcpuExit::IrqWindowOpen) => 3,
|
||||
Ok(VcpuExit::Hlt) => 4,
|
||||
Ok(VcpuExit::Shutdown) => 5,
|
||||
Ok(VcpuExit::FailEntry { .. }) => 6,
|
||||
Ok(VcpuExit::SystemEventShutdown) => 7,
|
||||
Ok(VcpuExit::SystemEventReset) => 7,
|
||||
Ok(VcpuExit::SystemEventCrash) => 7,
|
||||
Ok(VcpuExit::Intr) => 8,
|
||||
Ok(VcpuExit::Cpuid { .. }) => 9,
|
||||
Err(e) if e.errno() == ERROR_RETRY_I32 => 10,
|
||||
Err(_) => 11,
|
||||
Ok(VcpuExit::Canceled) => 12,
|
||||
_ => 13,
|
||||
}
|
||||
}
|
||||
|
||||
/// Give human readable names for each exit type that we've mapped to an exit index in exit_to_index.
|
||||
fn exit_index_to_str(exit: usize) -> String {
|
||||
(match exit {
|
||||
0 => "Io",
|
||||
1 => "Mmio",
|
||||
2 => "IoapicEoi",
|
||||
3 => "IrqWindowOpen",
|
||||
4 => "Hlt",
|
||||
5 => "Shutdown",
|
||||
6 => "FailEntry",
|
||||
7 => "SystemEvent",
|
||||
8 => "Intr",
|
||||
9 => "Cpuid",
|
||||
10 => "Retry",
|
||||
11 => "Error",
|
||||
12 => "Canceled",
|
||||
_ => "Unknown",
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Collects, merges, and displays statistics between vcpu threads.
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct StatisticsCollector {
|
||||
pub pio_bus_stats: Vec<BusStatistics>,
|
||||
pub mmio_bus_stats: Vec<BusStatistics>,
|
||||
pub vm_exit_stats: Vec<VmExitStatistics>,
|
||||
}
|
||||
|
||||
impl StatisticsCollector {
|
||||
pub fn new() -> StatisticsCollector {
|
||||
StatisticsCollector::default()
|
||||
}
|
||||
|
||||
/// Return a merged version of the pio bus statistics, mmio bus statistics, and the vm exit
|
||||
/// statistics for all vcpus.
|
||||
fn merged(&self) -> (BusStatistics, BusStatistics, VmExitStatistics) {
|
||||
(
|
||||
BusStatistics::merged(&self.pio_bus_stats),
|
||||
BusStatistics::merged(&self.mmio_bus_stats),
|
||||
VmExitStatistics::merged(&self.vm_exit_stats),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get a json representation of `self`. It contains two top-level keys: "vcpus" and "merged".
|
||||
/// The "vcpus" key's value is a list of per-vcpu stats, where the "merged" stats contains the
|
||||
/// sum of all vcpu stats.
|
||||
pub fn json(&self) -> serde_json::Value {
|
||||
let mut vcpus = serde_json::json!([]);
|
||||
let vcpus_vec = vcpus.as_array_mut().unwrap();
|
||||
|
||||
for i in 0..self.pio_bus_stats.len() {
|
||||
vcpus_vec.push(serde_json::json!({
|
||||
"io": self.pio_bus_stats[i].json(),
|
||||
"mmio": self.mmio_bus_stats[i].json(),
|
||||
"exits": self.vm_exit_stats[i].json(),
|
||||
}));
|
||||
}
|
||||
|
||||
let (pio, mmio, exits) = self.merged();
|
||||
|
||||
serde_json::json!({
|
||||
"merged": {
|
||||
"io": pio.json(),
|
||||
"mmio": mmio.json(),
|
||||
"exits": exits.json(),
|
||||
},
|
||||
"vcpus": vcpus
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for StatisticsCollector {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let (pio, mmio, exits) = self.merged();
|
||||
writeln!(f, "Port IO:")?;
|
||||
writeln!(f, "{}", pio)?;
|
||||
writeln!(f, "MMIO:")?;
|
||||
writeln!(f, "{}", mmio)?;
|
||||
writeln!(f, "Vm Exits:")?;
|
||||
writeln!(f, "{}", exits)
|
||||
}
|
||||
}
|
37
src/main.rs
37
src/main.rs
|
@ -32,8 +32,11 @@ use vm_control::{
|
|||
BalloonControlCommand, DiskControlCommand, UsbControlResult, VmRequest, VmResponse,
|
||||
};
|
||||
|
||||
use crate::sys::error_to_exit_code;
|
||||
use crate::sys::init_log;
|
||||
use crosvm::cmdline::{Command, CrossPlatformCommands, CrossPlatformDevicesCommands};
|
||||
#[cfg(windows)]
|
||||
use sys::windows::metrics;
|
||||
|
||||
#[cfg(feature = "scudo")]
|
||||
#[global_allocator]
|
||||
|
@ -91,6 +94,12 @@ where
|
|||
}
|
||||
}
|
||||
Ok(cfg) => {
|
||||
#[cfg(feature = "crash-report")]
|
||||
crosvm::sys::setup_emulator_crash_reporting(&cfg)?;
|
||||
|
||||
#[cfg(windows)]
|
||||
metrics::setup_metrics_reporting()?;
|
||||
|
||||
init_log(log_config, &cfg)?;
|
||||
let exit_state = crate::sys::run_config(cfg);
|
||||
to_command_status(exit_state)
|
||||
|
@ -463,9 +472,17 @@ fn prepare_argh_args<I: IntoIterator<Item = String>>(args_iter: I) -> Vec<String
|
|||
}
|
||||
|
||||
fn crosvm_main() -> Result<CommandStatus> {
|
||||
let _library_watcher = sys::get_library_watcher();
|
||||
|
||||
// The following panic hook will stop our crashpad hook on windows.
|
||||
// Only initialize when the crash-pad feature is off.
|
||||
#[cfg(not(feature = "crash-report"))]
|
||||
sys::set_panic_hook();
|
||||
|
||||
// Ensure all processes detach from metrics on exit.
|
||||
#[cfg(windows)]
|
||||
let _metrics_destructor = metrics::get_destructor();
|
||||
|
||||
let args = prepare_argh_args(std::env::args());
|
||||
let args = args.iter().map(|s| s.as_str()).collect::<Vec<_>>();
|
||||
let args = match crosvm::cmdline::CrosvmCmdlineArgs::from_args(&args[..1], &args[1..]) {
|
||||
|
@ -493,6 +510,16 @@ fn crosvm_main() -> Result<CommandStatus> {
|
|||
// We handle run_vm separately because it does not simply signal success/error
|
||||
// but also indicates whether the guest requested reset or stop.
|
||||
run_vm(cmd, log_config)
|
||||
} else if let CrossPlatformCommands::Device(cmd) = command {
|
||||
// On windows, the device command handles its own logging setup, so we can't handle it below
|
||||
// otherwise logging will double init.
|
||||
if cfg!(unix) {
|
||||
syslog::init_with(log_config)
|
||||
.map_err(|e| anyhow!("failed to initialize syslog: {}", e))?;
|
||||
}
|
||||
start_device(cmd)
|
||||
.map_err(|_| anyhow!("start_device subcommand failed"))
|
||||
.map(|_| CommandStatus::Success)
|
||||
} else {
|
||||
syslog::init_with(log_config)
|
||||
.map_err(|e| anyhow!("failed to initialize syslog: {}", e))?;
|
||||
|
@ -513,9 +540,7 @@ fn crosvm_main() -> Result<CommandStatus> {
|
|||
CrossPlatformCommands::CreateQcow2(cmd) => {
|
||||
create_qcow2(cmd).map_err(|_| anyhow!("create_qcow2 subcommand failed"))
|
||||
}
|
||||
CrossPlatformCommands::Device(cmd) => {
|
||||
start_device(cmd).map_err(|_| anyhow!("start_device subcommand failed"))
|
||||
}
|
||||
CrossPlatformCommands::Device(_) => unreachable!(),
|
||||
CrossPlatformCommands::Disk(cmd) => {
|
||||
disk_cmd(cmd).map_err(|_| anyhow!("disk subcommand failed"))
|
||||
}
|
||||
|
@ -590,7 +615,7 @@ fn main() {
|
|||
34
|
||||
}
|
||||
Err(e) => {
|
||||
let exit_code = 1;
|
||||
let exit_code = error_to_exit_code(&res);
|
||||
error!("exiting with error {}:{:?}", exit_code, e);
|
||||
exit_code
|
||||
}
|
||||
|
@ -611,6 +636,8 @@ mod tests {
|
|||
assert!(!is_flag("no-leading-dash"));
|
||||
}
|
||||
|
||||
// TODO(b/238361778) this doesn't work on Windows because is_flag isn't called yet.
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn args_split_long() {
|
||||
assert_eq!(
|
||||
|
@ -621,6 +648,8 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO(b/238361778) this doesn't work on Windows because is_flag isn't called yet.
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn args_split_short() {
|
||||
assert_eq!(
|
||||
|
|
12
src/sys.rs
12
src/sys.rs
|
@ -7,12 +7,22 @@ cfg_if::cfg_if! {
|
|||
pub(crate) mod unix;
|
||||
use unix as platform;
|
||||
pub(crate) use crate::crosvm::sys::unix::{run_config, ExitState};
|
||||
} else if #[cfg(windows)] {
|
||||
pub(crate) mod windows;
|
||||
use windows as platform;
|
||||
pub(crate) use windows::ExitState;
|
||||
pub(crate) use windows::run_config;
|
||||
} else {
|
||||
compile_error!("Unsupported platform");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use platform::main::{cleanup, init_log, run_command, start_device};
|
||||
pub(crate) use platform::main::{
|
||||
cleanup, error_to_exit_code, get_library_watcher, init_log, run_command, start_device,
|
||||
};
|
||||
|
||||
#[cfg(feature = "kiwi")]
|
||||
pub(crate) use platform::main::sandbox_lower_token;
|
||||
|
||||
#[cfg(not(feature = "crash-report"))]
|
||||
pub(crate) use platform::set_panic_hook;
|
||||
|
|
|
@ -16,7 +16,7 @@ use devices::virtio::vhost::user::device::{
|
|||
|
||||
use crate::{
|
||||
crosvm::sys::cmdline::{Commands, DevicesSubcommand},
|
||||
Config,
|
||||
CommandStatus, Config,
|
||||
};
|
||||
|
||||
pub(crate) fn start_device(command: DevicesSubcommand) -> anyhow::Result<()> {
|
||||
|
@ -74,6 +74,10 @@ pub(crate) fn cleanup() {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_library_watcher() -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn run_command(_cmd: Commands) -> anyhow::Result<()> {
|
||||
Err(anyhow::anyhow!("invalid command"))
|
||||
}
|
||||
|
@ -88,3 +92,7 @@ where
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn error_to_exit_code(_res: &std::result::Result<CommandStatus, anyhow::Error>) -> i32 {
|
||||
1
|
||||
}
|
||||
|
|
2078
src/sys/windows.rs
Normal file
2078
src/sys/windows.rs
Normal file
File diff suppressed because it is too large
Load diff
364
src/sys/windows/irq_wait.rs
Normal file
364
src/sys/windows/irq_wait.rs
Normal file
|
@ -0,0 +1,364 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! Handles the main wait loop for IRQs.
|
||||
//! Should be started on a background thread.
|
||||
|
||||
use base::{
|
||||
error, info, warn, Event, EventToken, ReadNotifier, Result, Tube, TubeError, WaitContext,
|
||||
MAXIMUM_WAIT_OBJECTS,
|
||||
};
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
use devices::IrqChipAArch64 as IrqChipArch;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
use devices::IrqChipX86_64 as IrqChipArch;
|
||||
use devices::{IrqEdgeEvent, IrqEventIndex, IrqEventSource};
|
||||
use metrics::{log_high_frequency_descriptor_event, MetricEventType};
|
||||
use resources::SystemAllocator;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::thread::{self, JoinHandle};
|
||||
use std::time::{Duration, Instant};
|
||||
use sync::Mutex;
|
||||
use vm_control::{IrqSetup, VmIrqRequest};
|
||||
|
||||
pub struct IrqWaitWorker {
|
||||
exit_evt: Event,
|
||||
irq_chip: Box<dyn IrqChipArch>,
|
||||
irq_control_tubes: Vec<Tube>,
|
||||
sys_allocator: Arc<Mutex<SystemAllocator>>,
|
||||
}
|
||||
|
||||
impl IrqWaitWorker {
|
||||
pub fn start(
|
||||
exit_evt: Event,
|
||||
irq_chip: Box<dyn IrqChipArch>,
|
||||
irq_control_tubes: Vec<Tube>,
|
||||
sys_allocator: Arc<Mutex<SystemAllocator>>,
|
||||
) -> JoinHandle<Result<()>> {
|
||||
let mut irq_worker = IrqWaitWorker {
|
||||
exit_evt,
|
||||
irq_chip,
|
||||
irq_control_tubes,
|
||||
sys_allocator,
|
||||
};
|
||||
thread::Builder::new()
|
||||
.name("irq_wait_loop".into())
|
||||
.spawn(move || irq_worker.run())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn run(&mut self) -> Result<()> {
|
||||
#[derive(EventToken)]
|
||||
enum Token {
|
||||
Exit,
|
||||
VmControl { index: usize },
|
||||
DelayedIrqEvent,
|
||||
}
|
||||
|
||||
let wait_ctx = WaitContext::build_with(&[(&self.exit_evt, Token::Exit)])?;
|
||||
|
||||
let mut max_event_index: usize = 0;
|
||||
let mut vm_control_added_irq_events: Vec<Event> = Vec::new();
|
||||
let mut irq_event_sources: HashMap<IrqEventIndex, IrqEventSource> = HashMap::new();
|
||||
// TODO(b/190828888): Move irq logging into the irqchip impls.
|
||||
let irq_frequencies = Arc::new(Mutex::new(vec![0; max_event_index + 1]));
|
||||
let irq_events = self.irq_chip.irq_event_tokens()?;
|
||||
let mut children = vec![];
|
||||
|
||||
let (mut child_wait_ctx, child_join_handle) = IrqWaitWorkerChild::start(
|
||||
self.exit_evt.try_clone()?,
|
||||
self.irq_chip.try_box_clone()?,
|
||||
irq_frequencies.clone(),
|
||||
)?;
|
||||
children.push(child_join_handle);
|
||||
|
||||
for (event_index, source, evt) in irq_events {
|
||||
child_wait_ctx.add(&evt, ChildToken::IrqEvent { event_index })?;
|
||||
max_event_index = std::cmp::max(max_event_index, event_index);
|
||||
irq_event_sources.insert(event_index, source);
|
||||
|
||||
vm_control_added_irq_events.push(evt);
|
||||
}
|
||||
|
||||
irq_frequencies.lock().resize(max_event_index + 1, 0);
|
||||
|
||||
for (index, control_tube) in self.irq_control_tubes.iter().enumerate() {
|
||||
wait_ctx.add(control_tube.get_read_notifier(), Token::VmControl { index })?;
|
||||
}
|
||||
|
||||
let mut _delayed_event_token: Option<Event> = None;
|
||||
if let Some(delayed_token) = self.irq_chip.irq_delayed_event_token()? {
|
||||
wait_ctx.add(&delayed_token, Token::DelayedIrqEvent)?;
|
||||
// store the token, so that it lasts outside this scope.
|
||||
// We must store the event as try_clone creates a new event. It won't keep
|
||||
// the current event valid that is waited on inside wait_ctx.
|
||||
_delayed_event_token = Some(delayed_token);
|
||||
}
|
||||
|
||||
let mut intr_stat_sample_time = Instant::now();
|
||||
|
||||
'poll: loop {
|
||||
let events = {
|
||||
match wait_ctx.wait() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed to wait on irq thread: {}", e);
|
||||
break 'poll;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut vm_control_indices_to_remove = Vec::new();
|
||||
for event in events.iter().filter(|e| e.is_readable) {
|
||||
match event.token {
|
||||
Token::Exit => {
|
||||
info!("irq event loop got exit event");
|
||||
break 'poll;
|
||||
}
|
||||
Token::VmControl { index } => {
|
||||
if let Some(tube) = self.irq_control_tubes.get(index) {
|
||||
match tube.recv::<VmIrqRequest>() {
|
||||
Ok(request) => {
|
||||
let response = {
|
||||
let irq_chip = &mut self.irq_chip;
|
||||
let exit_evt = &self.exit_evt;
|
||||
// TODO(b/229262201): Refactor the closure into a standalone function to reduce indentation.
|
||||
request.execute(
|
||||
|setup| match setup {
|
||||
IrqSetup::Event(
|
||||
irq,
|
||||
ev,
|
||||
device_id,
|
||||
queue_id,
|
||||
device_name,
|
||||
) => {
|
||||
let irqevent = IrqEdgeEvent::from_event(
|
||||
ev.try_clone()
|
||||
.expect("Failed to clone irq event."),
|
||||
);
|
||||
let source = IrqEventSource {
|
||||
device_id: device_id.try_into()?,
|
||||
queue_id,
|
||||
device_name,
|
||||
};
|
||||
let event_index = irq_chip
|
||||
.register_edge_irq_event(
|
||||
irq,
|
||||
&irqevent,
|
||||
source.clone(),
|
||||
)?;
|
||||
if let Some(event_index) = event_index {
|
||||
max_event_index = std::cmp::max(
|
||||
event_index,
|
||||
irq as usize,
|
||||
);
|
||||
irq_frequencies
|
||||
.lock()
|
||||
.resize(max_event_index + 1, 0);
|
||||
irq_event_sources
|
||||
.insert(event_index, source);
|
||||
// Make new thread if needed, including buffer space for any
|
||||
// events we didn't explicitly add (exit/reset/etc)
|
||||
if irq_event_sources.len()
|
||||
% (MAXIMUM_WAIT_OBJECTS - 3)
|
||||
== 0
|
||||
{
|
||||
// The child wait thread has reached max capacity, we
|
||||
// need to add another.
|
||||
let (new_wait_ctx, child_join_handle) =
|
||||
IrqWaitWorkerChild::start(
|
||||
exit_evt.try_clone()?,
|
||||
irq_chip.try_box_clone()?,
|
||||
irq_frequencies.clone(),
|
||||
)?;
|
||||
child_wait_ctx = new_wait_ctx;
|
||||
children.push(child_join_handle);
|
||||
}
|
||||
let irqevent =
|
||||
irqevent.get_trigger().try_clone()?;
|
||||
match child_wait_ctx.add(
|
||||
&irqevent,
|
||||
ChildToken::IrqEvent { event_index },
|
||||
) {
|
||||
Err(e) => {
|
||||
warn!("failed to add IrqEvent to synchronization context: {}", e);
|
||||
Err(e)
|
||||
},
|
||||
Ok(_) => {
|
||||
vm_control_added_irq_events
|
||||
.push(irqevent);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
IrqSetup::Route(route) => irq_chip.route_irq(route),
|
||||
IrqSetup::UnRegister(irq, ev) => irq_chip
|
||||
.unregister_edge_irq_event(
|
||||
irq,
|
||||
&IrqEdgeEvent::from_event(ev.try_clone()?),
|
||||
),
|
||||
},
|
||||
&mut self.sys_allocator.lock(),
|
||||
)
|
||||
};
|
||||
if let Err(e) = tube.send(&response) {
|
||||
error!("failed to send VmIrqResponse: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if let TubeError::Disconnected = e {
|
||||
vm_control_indices_to_remove.push(index);
|
||||
} else {
|
||||
error!("failed to recv VmIrqRequest: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Token::DelayedIrqEvent => {
|
||||
if let Err(e) = self.irq_chip.process_delayed_irq_events() {
|
||||
warn!("can't deliver delayed irqs: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
let intr_stat_duration = now.duration_since(intr_stat_sample_time);
|
||||
|
||||
// include interrupt stats every 10 seconds
|
||||
if intr_stat_duration > Duration::from_secs(10) {
|
||||
let mut event_indices: Vec<(&usize, &IrqEventSource)> =
|
||||
irq_event_sources.iter().collect();
|
||||
// sort the devices by irq_frequency
|
||||
let mut locked_irq_frequencies = irq_frequencies.lock();
|
||||
event_indices
|
||||
.sort_by_key(|(idx, _)| std::cmp::Reverse(locked_irq_frequencies[**idx]));
|
||||
let rates: Vec<String> = event_indices
|
||||
.iter()
|
||||
.filter(|(idx, _)| locked_irq_frequencies[**idx] > 0)
|
||||
.map(|(idx, source)| {
|
||||
let rate = locked_irq_frequencies[**idx] / intr_stat_duration.as_secs();
|
||||
// As the descriptor, use a 64bit int containing two 32bit ids.
|
||||
// low bits: queue_id, high bits: device_id
|
||||
let descriptor_bytes: [u8; 8] = {
|
||||
let mut bytes: [u8; 8] = [0; 8];
|
||||
for (i, byte) in
|
||||
(source.queue_id as u32).to_le_bytes().iter().enumerate()
|
||||
{
|
||||
bytes[i] = *byte
|
||||
}
|
||||
let device_id: u32 = source.device_id.into();
|
||||
for (i, byte) in device_id.to_le_bytes().iter().enumerate() {
|
||||
bytes[i + 4] = *byte
|
||||
}
|
||||
bytes
|
||||
};
|
||||
log_high_frequency_descriptor_event(
|
||||
MetricEventType::Interrupts,
|
||||
i64::from_le_bytes(descriptor_bytes),
|
||||
rate as i64,
|
||||
);
|
||||
format!("{}({})->{}/s", source.device_name, source.queue_id, rate,)
|
||||
})
|
||||
.collect();
|
||||
|
||||
info!("crosvm-interrupt-rates: {}", rates.join(", "));
|
||||
|
||||
// reset sample time and counters
|
||||
intr_stat_sample_time = now;
|
||||
*locked_irq_frequencies = vec![0; max_event_index + 1];
|
||||
}
|
||||
|
||||
vm_control_indices_to_remove.dedup();
|
||||
for index in vm_control_indices_to_remove {
|
||||
self.irq_control_tubes.swap_remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure all children have ended by firing off the exit event again to make sure the loop
|
||||
// is exited, and joining to ensure none are hanging.
|
||||
let _ = self.exit_evt.write(1);
|
||||
for child in children {
|
||||
match child.join() {
|
||||
Ok(Err(e)) => warn!("IRQ woker child ended in error: {}", e),
|
||||
Err(e) => warn!("IRQ worker child panicked with error: {:?}", e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(EventToken)]
|
||||
enum ChildToken {
|
||||
Exit,
|
||||
IrqEvent { event_index: IrqEventIndex },
|
||||
}
|
||||
/// An arbitrarily expandible worker for waiting on irq events.
|
||||
/// This worker is responsible for hadling the irq events, whereas
|
||||
/// the parent worker's job is just to handle the irq control tube requests.
|
||||
struct IrqWaitWorkerChild {
|
||||
wait_ctx: Arc<WaitContext<ChildToken>>,
|
||||
exit_evt: Event,
|
||||
irq_chip: Box<dyn IrqChipArch>,
|
||||
irq_frequencies: Arc<Mutex<Vec<u64>>>,
|
||||
}
|
||||
|
||||
impl IrqWaitWorkerChild {
|
||||
fn start(
|
||||
exit_evt: Event,
|
||||
irq_chip: Box<dyn IrqChipArch>,
|
||||
irq_frequencies: Arc<Mutex<Vec<u64>>>,
|
||||
) -> Result<(Arc<WaitContext<ChildToken>>, JoinHandle<Result<()>>)> {
|
||||
let child_wait_ctx = Arc::new(WaitContext::new()?);
|
||||
let mut child = IrqWaitWorkerChild {
|
||||
wait_ctx: child_wait_ctx.clone(),
|
||||
exit_evt,
|
||||
irq_chip,
|
||||
irq_frequencies,
|
||||
};
|
||||
let join_handle = thread::Builder::new()
|
||||
.name("irq_child_wait_loop".into())
|
||||
.spawn(move || child.run())?;
|
||||
|
||||
Ok((child_wait_ctx, join_handle))
|
||||
}
|
||||
|
||||
fn run(&mut self) -> Result<()> {
|
||||
self.wait_ctx.add(&self.exit_evt, ChildToken::Exit)?;
|
||||
'poll: loop {
|
||||
let events = {
|
||||
match self.wait_ctx.wait() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed to wait on irq child thread: {}", e);
|
||||
break 'poll;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().filter(|e| e.is_readable) {
|
||||
match event.token {
|
||||
ChildToken::Exit => {
|
||||
info!("irq child event loop got exit event");
|
||||
break 'poll;
|
||||
}
|
||||
ChildToken::IrqEvent { event_index } => {
|
||||
self.irq_frequencies.lock()[event_index] += 1;
|
||||
if let Err(e) = self.irq_chip.service_irq_event(event_index) {
|
||||
error!("failed to signal irq {}: {}", event_index, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
247
src/sys/windows/main.rs
Normal file
247
src/sys/windows/main.rs
Normal file
|
@ -0,0 +1,247 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use argh::FromArgs;
|
||||
use base::{
|
||||
info,
|
||||
syslog::{self, LogConfig},
|
||||
FromRawDescriptor, RawDescriptor,
|
||||
};
|
||||
use broker_ipc::{common_child_setup, CommonChildStartupArgs};
|
||||
use metrics::{
|
||||
self,
|
||||
event_details_proto::{EmulatorDllDetails, RecordDetails},
|
||||
MetricEventType,
|
||||
};
|
||||
#[cfg(all(feature = "slirp"))]
|
||||
use net_util::slirp::sys::windows::SlirpStartupConfig;
|
||||
use tube_transporter::{TubeToken, TubeTransporterReader};
|
||||
use win_util::{DllNotificationData, DllWatcher};
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::OpenOptions;
|
||||
|
||||
use crate::{
|
||||
crosvm::{
|
||||
argument::{self, Argument},
|
||||
cmdline::RunCommand,
|
||||
sys::cmdline::{Commands, DevicesSubcommand},
|
||||
sys::windows::exit::{Exit, ExitContext, ExitContextAnyhow},
|
||||
},
|
||||
metrics::run_metrics,
|
||||
CommandStatus, Config,
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "slirp"))]
|
||||
pub(crate) fn run_slirp(args: Vec<String>) -> Result<()> {
|
||||
let arguments = &[Argument::value(
|
||||
"bootstrap",
|
||||
"TRANSPORT_TUBE_RD",
|
||||
"TubeTransporter descriptor used to bootstrap the Slirp process.",
|
||||
)];
|
||||
|
||||
let raw_transport_tube = set_bootstrap_arguments(args, arguments)
|
||||
.exit_context(Exit::InvalidSubCommandArgs, "error in setting slirp args")?;
|
||||
|
||||
// Safe because we know that raw_transport_tube is valid (passed by inheritance),
|
||||
// and that the blocking & framing modes are accurate because we create them ourselves
|
||||
// in the broker.
|
||||
let tube_transporter =
|
||||
unsafe { TubeTransporterReader::from_raw_descriptor(raw_transport_tube.unwrap()) };
|
||||
|
||||
let mut tube_data_list = tube_transporter
|
||||
.read_tubes()
|
||||
.exit_context(Exit::TubeTransporterInit, "failed to initialize tube")?;
|
||||
|
||||
let bootstrap_tube = tube_data_list.get_tube(TubeToken::Bootstrap).unwrap();
|
||||
|
||||
let startup_args: CommonChildStartupArgs =
|
||||
bootstrap_tube.recv::<CommonChildStartupArgs>().unwrap();
|
||||
let _child_cleanup = common_child_setup(startup_args).exit_context(
|
||||
Exit::CommonChildSetupError,
|
||||
"failed to perform common child setup",
|
||||
)?;
|
||||
|
||||
let slirp_config = bootstrap_tube.recv::<SlirpStartupConfig>().unwrap();
|
||||
|
||||
if let Some(mut target) = sandbox::TargetServices::get()
|
||||
.exit_context(Exit::SandboxError, "sandbox operation failed")?
|
||||
{
|
||||
target.lower_token();
|
||||
}
|
||||
|
||||
net_util::Slirp::run_slirp_process(
|
||||
slirp_config.slirp_pipe,
|
||||
slirp_config.shutdown_event,
|
||||
#[cfg(feature = "slirp-ring-capture")]
|
||||
slirp_config.slirp_capture_file,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_broker_impl(cfg: Config) -> Result<()> {
|
||||
tracing::init();
|
||||
Ok(crate::crosvm::sys::windows::broker::run(cfg)?)
|
||||
}
|
||||
|
||||
pub fn initialize_sandbox() -> Result<()> {
|
||||
if sandbox::is_sandbox_target() {
|
||||
// Get the TargetServices pointer so that it gets initialized.
|
||||
let _ = sandbox::TargetServices::get()
|
||||
.exit_context(Exit::SandboxError, "sandbox operation failed")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "kiwi")]
|
||||
pub fn sandbox_lower_token() -> Result<()> {
|
||||
if let Some(mut target) = sandbox::TargetServices::get()
|
||||
.exit_context(Exit::SandboxError, "sandbox operation failed")?
|
||||
{
|
||||
target.lower_token();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn report_dll_loaded(dll_name: String) {
|
||||
let mut dll_load_details = EmulatorDllDetails::new();
|
||||
dll_load_details.set_dll_base_name(dll_name);
|
||||
let mut details = RecordDetails::new();
|
||||
details.set_emulator_dll_details(dll_load_details);
|
||||
metrics::log_event_with_details(MetricEventType::DllLoaded, &details);
|
||||
}
|
||||
|
||||
pub fn get_library_watcher(
|
||||
) -> std::io::Result<DllWatcher<impl FnMut(DllNotificationData), impl FnMut(DllNotificationData)>> {
|
||||
let mut dlls: HashSet<OsString> = HashSet::new();
|
||||
DllWatcher::new(
|
||||
move |data| {
|
||||
info!("DLL loaded: {:?}", data.base_dll_name);
|
||||
if !dlls.insert(data.base_dll_name.clone()) && metrics::is_initialized() {
|
||||
report_dll_loaded(data.base_dll_name.to_string_lossy().into_owned());
|
||||
}
|
||||
},
|
||||
|data| info!("DLL unloaded: {:?}", data.base_dll_name),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn start_device(command: DevicesSubcommand) -> Result<()> {
|
||||
Err(anyhow!("unknown device name: {:?}", command))
|
||||
}
|
||||
|
||||
pub(crate) fn run_vm_for_broker(args: Vec<String>) -> Result<()> {
|
||||
// This is a noop on unix.
|
||||
initialize_sandbox()?;
|
||||
let arguments = &[Argument::value(
|
||||
"bootstrap",
|
||||
"TRANSPORT_TUBE_RD",
|
||||
"TubeTransporter descriptor used to bootstrap the main process.",
|
||||
)];
|
||||
|
||||
let raw_transport_tube = set_bootstrap_arguments(args, arguments).exit_context(
|
||||
Exit::InvalidSubCommandArgs,
|
||||
"error in setting crosvm broker args",
|
||||
)?;
|
||||
let exit_state = crate::sys::windows::run_config_for_broker(raw_transport_tube.unwrap());
|
||||
crate::to_command_status(exit_state).map(|_| ())
|
||||
}
|
||||
|
||||
pub(crate) fn set_bootstrap_arguments(
|
||||
args: Vec<String>,
|
||||
arguments: &[Argument],
|
||||
) -> std::result::Result<Option<RawDescriptor>, argument::Error> {
|
||||
let mut raw_transport_tube = None;
|
||||
crate::crosvm::argument::set_arguments(args.iter(), &arguments[..], |name, value| {
|
||||
if name == "bootstrap" {
|
||||
raw_transport_tube = Some(value.unwrap().parse::<usize>().or(Err(
|
||||
argument::Error::InvalidValue {
|
||||
value: value.unwrap().to_string(),
|
||||
expected: String::from("a raw descriptor integer"),
|
||||
},
|
||||
))? as RawDescriptor);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.expect("Failed to set bootstrap arguments");
|
||||
Ok(raw_transport_tube)
|
||||
}
|
||||
|
||||
pub(crate) fn cleanup() {
|
||||
// We've already cleaned everything up by waiting for all the vcpu threads on windows.
|
||||
// TODO: b/142733266. When we sandbox each device, have a way to terminate the other sandboxed processes.
|
||||
}
|
||||
|
||||
fn run_broker(cmd: RunCommand) -> Result<()> {
|
||||
match TryInto::<Config>::try_into(cmd) {
|
||||
Ok(cfg) => run_broker_impl(cfg),
|
||||
Err(e) => Err(anyhow!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_command(cmd: Commands) -> anyhow::Result<()> {
|
||||
match cmd {
|
||||
Commands::RunMetrics(cmd) => run_metrics(cmd.args),
|
||||
Commands::RunMP(cmd) => {
|
||||
let mut x: Vec<&str> = vec![];
|
||||
for s in cmd.args.iter() {
|
||||
if s == "backend=win_audio" {
|
||||
x.push(&s.as_str());
|
||||
continue;
|
||||
}
|
||||
match s.split_once('=') {
|
||||
Some((k, v)) => {
|
||||
x.push(&k);
|
||||
x.push(&v);
|
||||
}
|
||||
None => x.push(s.as_str()),
|
||||
}
|
||||
}
|
||||
let cmd = RunCommand::from_args(&["run-mp"], &x);
|
||||
match cmd {
|
||||
Ok(cmd) => run_broker(cmd),
|
||||
Err(e) => Err(anyhow!("Failed to create config: {:?}", e)),
|
||||
}
|
||||
}
|
||||
Commands::RunMain(cmd) => run_vm_for_broker(cmd.args),
|
||||
#[cfg(feature = "slirp")]
|
||||
Commands::RunSlirp(cmd) => run_slirp(cmd.args),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn init_log<F: 'static>(log_config: LogConfig<F>, cfg: &Config) -> Result<()>
|
||||
where
|
||||
F: Fn(&mut base::syslog::fmt::Formatter, &log::Record<'_>) -> std::io::Result<()> + Sync + Send,
|
||||
{
|
||||
if let Err(e) = syslog::init_with(LogConfig {
|
||||
proc_name: if let Some(ref tag) = cfg.syslog_tag {
|
||||
tag.to_string()
|
||||
} else {
|
||||
String::from("crosvm")
|
||||
},
|
||||
pipe: if let Some(log_file_path) = &cfg.log_file {
|
||||
let file = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(log_file_path)
|
||||
.with_exit_context(Exit::LogFile, || {
|
||||
format!("failed to open log file {}", log_file_path)
|
||||
})?;
|
||||
Some(Box::new(file))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
stderr: if cfg.log_file.is_some() { false } else { true },
|
||||
..log_config
|
||||
}) {
|
||||
eprintln!("failed to initialize syslog: {}", e);
|
||||
return Err(anyhow!("failed to initialize syslog: {}", e));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn error_to_exit_code(res: &std::result::Result<CommandStatus, anyhow::Error>) -> i32 {
|
||||
res.to_exit_code().unwrap_or(Exit::UnknownError.into())
|
||||
}
|
93
src/sys/windows/metrics.rs
Normal file
93
src/sys/windows/metrics.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "kiwi")] {
|
||||
extern crate metrics as metrics_crate;
|
||||
use anyhow::{Context};
|
||||
use broker_ipc::{common_child_setup, CommonChildStartupArgs};
|
||||
use base::Tube;
|
||||
use std::thread;
|
||||
use metrics_crate::MetricsController;
|
||||
use crate::crosvm::sys::windows::exit::{Exit, ExitContext, ExitContextAnyhow};
|
||||
use crate::sys::windows::main::set_bootstrap_arguments;
|
||||
use tube_transporter::{TubeToken, TubeTransporterReader};
|
||||
use base::FromRawDescriptor;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "kiwi")]
|
||||
use crate::crosvm::argument::Argument;
|
||||
use anyhow::Result;
|
||||
pub(crate) use metrics::{
|
||||
get_destructor, log_descriptor, merge_session_invariants, set_auth_token, set_package_name,
|
||||
MetricEventType,
|
||||
};
|
||||
|
||||
pub(crate) fn run_metrics(#[allow(unused_variables)] args: Vec<String>) -> Result<()> {
|
||||
#[cfg(not(feature = "kiwi"))]
|
||||
return Ok(());
|
||||
|
||||
#[cfg(feature = "kiwi")]
|
||||
{
|
||||
let arguments = &[Argument::value(
|
||||
"bootstrap",
|
||||
"TRANSPORT_TUBE_RD",
|
||||
"TubeTransporter descriptor used to bootstrap the metrics process.",
|
||||
)];
|
||||
|
||||
let raw_transport_tube = set_bootstrap_arguments(args, arguments).exit_context(
|
||||
Exit::InvalidSubCommandArgs,
|
||||
"error in setting crosvm metrics controller args",
|
||||
)?;
|
||||
|
||||
// Safe because we know that raw_transport_tube is valid (passed by inheritance), and that the
|
||||
// blocking & framing modes are accurate because we create them ourselves in the broker.
|
||||
let tube_transporter =
|
||||
unsafe { TubeTransporterReader::from_raw_descriptor(raw_transport_tube.unwrap()) };
|
||||
|
||||
let mut tube_data_list = tube_transporter
|
||||
.read_tubes()
|
||||
.exit_context(Exit::TubeTransporterInit, "failed to initialize tube")?;
|
||||
|
||||
let bootstrap_tube = tube_data_list.get_tube(TubeToken::Bootstrap).unwrap();
|
||||
|
||||
let startup_args: CommonChildStartupArgs =
|
||||
bootstrap_tube.recv::<CommonChildStartupArgs>().unwrap();
|
||||
let _child_cleanup = common_child_setup(startup_args).exit_context(
|
||||
Exit::CommonChildSetupError,
|
||||
"failed to perform common child setup",
|
||||
)?;
|
||||
|
||||
let metrics_tubes = bootstrap_tube.recv::<Vec<Tube>>().unwrap();
|
||||
|
||||
tracing::init();
|
||||
crate::sys::sandbox_lower_token()?;
|
||||
|
||||
let mut metrics_controller = MetricsController::new(metrics_tubes);
|
||||
metrics_controller
|
||||
.run()
|
||||
.exit_context(Exit::MetricsController, "metrics controller failed")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn setup_metrics_reporting() -> Result<()> {
|
||||
#[cfg(not(feature = "kiwi"))]
|
||||
return Ok(());
|
||||
|
||||
#[cfg(feature = "kiwi")]
|
||||
{
|
||||
let (metrics_controller_tube, metrics_agent_tube) =
|
||||
Tube::pair().exit_context(Exit::CreateTube, "failed to create tube")?;
|
||||
thread::spawn(move || {
|
||||
let mut metrics_controller = MetricsController::new(vec![metrics_controller_tube]);
|
||||
metrics_controller
|
||||
.run()
|
||||
.context("metrics controller failed")
|
||||
.unwrap();
|
||||
});
|
||||
metrics::initialize(metrics_agent_tube);
|
||||
Ok(())
|
||||
}
|
||||
}
|
26
src/sys/windows/panic_hook.rs
Normal file
26
src/sys/windows/panic_hook.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::panic;
|
||||
use std::process::abort;
|
||||
|
||||
use crate::metrics;
|
||||
|
||||
/// The intent of our panic hook is to get panic info and a stacktrace into the syslog, even for
|
||||
/// jailed subprocesses. It will always abort on panic to ensure a minidump is generated.
|
||||
///
|
||||
/// Note that jailed processes will usually have a stacktrace of <unknown> because the backtrace
|
||||
/// routines attempt to open this binary and are unable to do so in a jail.
|
||||
pub fn set_panic_hook() {
|
||||
let default_panic = panic::take_hook();
|
||||
panic::set_hook(Box::new(move |info| {
|
||||
// Ensure all in-flight metrics are fully flushed
|
||||
metrics::get_destructor().cleanup();
|
||||
// TODO(b/144724919): should update log_panic_info for this "cleanly exit crosvm" bug
|
||||
// log_panic_info(default_panic.as_ref(), info);
|
||||
default_panic(info);
|
||||
// Abort to trigger the crash reporter so that a minidump is generated.
|
||||
abort();
|
||||
}));
|
||||
}
|
922
src/sys/windows/run_vcpu.rs
Normal file
922
src/sys/windows/run_vcpu.rs
Normal file
|
@ -0,0 +1,922 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use arch::{self, LinuxArch, RunnableLinuxVm, VcpuAffinity};
|
||||
use base::{
|
||||
self, error, info, set_audio_thread_priorities, set_cpu_affinity, warn, Event,
|
||||
Result as BaseResult, SafeMultimediaHandle, SendTube, Timer, Tube, VmEventType,
|
||||
};
|
||||
use std::{
|
||||
arch::x86_64::{__cpuid, __cpuid_count},
|
||||
fmt::Display,
|
||||
};
|
||||
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
use {
|
||||
aarch64::AArch64 as Arch,
|
||||
devices::{IrqChip, IrqChipAArch64 as IrqChipArch},
|
||||
hypervisor::{VcpuAArch64 as VcpuArch, VmAArch64 as VmArch},
|
||||
};
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
use {
|
||||
devices::IrqChipX86_64 as IrqChipArch,
|
||||
hypervisor::{VcpuX86_64 as VcpuArch, VmX86_64 as VmArch},
|
||||
x86_64::{adjust_cpuid, CpuIdContext, X8664arch as Arch},
|
||||
};
|
||||
|
||||
use crate::bail_exit_code;
|
||||
use crate::crosvm::sys::windows::exit::{Exit, ExitContext, ExitContextAnyhow};
|
||||
use crate::crosvm::sys::windows::stats::{StatisticsCollector, VmExitStatistics};
|
||||
use crate::sys::windows::save_vcpu_tsc_offset;
|
||||
use cros_async::{select2, EventAsync, Executor, SelectResult, TimerAsync};
|
||||
use devices::{Bus, TscSyncMitigations, VcpuRunState};
|
||||
use futures::pin_mut;
|
||||
#[cfg(feature = "whpx")]
|
||||
use hypervisor::whpx::WhpxVcpu;
|
||||
use hypervisor::{
|
||||
HypervisorCap, IoEventAddress, IoOperation, IoParams, VcpuExit, VcpuInitX86_64, VcpuRunHandle,
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{fmt, thread};
|
||||
use sync::{Condvar, Mutex};
|
||||
use tracing::trace_event;
|
||||
use vm_control::VmRunMode;
|
||||
use winapi::shared::winerror::ERROR_RETRY;
|
||||
|
||||
use crate::sys::windows::ExitState;
|
||||
|
||||
const ERROR_RETRY_I32: i32 = ERROR_RETRY as i32;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct VcpuRunMode {
|
||||
mtx: Mutex<VmRunMode>,
|
||||
cvar: Condvar,
|
||||
}
|
||||
|
||||
impl VcpuRunMode {
|
||||
pub fn set_and_notify(&self, new_mode: VmRunMode) {
|
||||
*self.mtx.lock() = new_mode;
|
||||
self.cvar.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
struct RunnableVcpuInfo<V> {
|
||||
vcpu: V,
|
||||
thread_priority_handle: Option<SafeMultimediaHandle>,
|
||||
vcpu_run_handle: VcpuRunHandle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct VcpuMonitoringMetadata {
|
||||
pub start_instant: Instant,
|
||||
// Milliseconds since the baseline start_instant
|
||||
pub last_run_time: Arc<AtomicU64>,
|
||||
pub last_exit_snapshot: Arc<Mutex<Option<VcpuExitData>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct VcpuRunThread {
|
||||
pub cpu_id: usize,
|
||||
pub monitoring_metadata: Option<VcpuMonitoringMetadata>,
|
||||
}
|
||||
|
||||
impl VcpuRunThread {
|
||||
pub fn new(cpu_id: usize, enable_vcpu_monitoring: bool) -> VcpuRunThread {
|
||||
VcpuRunThread {
|
||||
cpu_id,
|
||||
monitoring_metadata: enable_vcpu_monitoring.then(|| VcpuMonitoringMetadata {
|
||||
start_instant: Instant::now(),
|
||||
last_run_time: Arc::new(AtomicU64::new(0)),
|
||||
last_exit_snapshot: Arc::new(Mutex::new(Option::None)),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform WHPX-specific vcpu configurations
|
||||
#[cfg(feature = "whpx")]
|
||||
fn whpx_configure_vcpu(vcpu: &mut dyn VcpuArch, irq_chip: &mut dyn IrqChipArch) {
|
||||
// only apply to actual WhpxVcpu instances
|
||||
if let Some(whpx_vcpu) = vcpu.downcast_mut::<WhpxVcpu>() {
|
||||
// WhpxVcpu instances need to know the TSC and Lapic frequencies to handle Hyper-V MSR reads
|
||||
// and writes.
|
||||
let tsc_freq = devices::tsc_frequency()
|
||||
.map_err(|e| {
|
||||
error!(
|
||||
"Could not determine TSC frequency, WHPX vcpu will not be configured with \
|
||||
a TSC Frequency: {e}"
|
||||
);
|
||||
e
|
||||
})
|
||||
.ok();
|
||||
whpx_vcpu.set_frequencies(tsc_freq, irq_chip.lapic_frequency());
|
||||
}
|
||||
}
|
||||
|
||||
// Sets up a vcpu and converts it into a runnable vcpu.
|
||||
fn runnable_vcpu<V>(
|
||||
cpu_id: usize,
|
||||
vcpu: Option<V>,
|
||||
vcpu_init: VcpuInitX86_64,
|
||||
vm: &impl VmArch,
|
||||
irq_chip: &mut dyn IrqChipArch,
|
||||
vcpu_count: usize,
|
||||
run_rt: bool,
|
||||
vcpu_affinity: Option<Vec<usize>>,
|
||||
no_smt: bool,
|
||||
has_bios: bool,
|
||||
host_cpu_topology: bool,
|
||||
force_calibrated_tsc_leaf: bool,
|
||||
) -> Result<RunnableVcpuInfo<V>>
|
||||
where
|
||||
V: VcpuArch,
|
||||
{
|
||||
let mut vcpu = match vcpu {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
// If vcpu is None, it means this arch/hypervisor requires create_vcpu to be called from
|
||||
// the vcpu thread.
|
||||
match vm
|
||||
.create_vcpu(cpu_id)
|
||||
.exit_context(Exit::CreateVcpu, "failed to create vcpu")?
|
||||
.downcast::<V>()
|
||||
{
|
||||
Ok(v) => *v,
|
||||
Err(_) => panic!("VM created wrong type of VCPU"),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
irq_chip
|
||||
.add_vcpu(cpu_id, &vcpu)
|
||||
.exit_context(Exit::AddIrqChipVcpu, "failed to add vcpu to irq chip")?;
|
||||
|
||||
if let Some(affinity) = vcpu_affinity {
|
||||
if let Err(e) = set_cpu_affinity(affinity) {
|
||||
error!("Failed to set CPU affinity: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Arch::configure_vcpu(
|
||||
vm,
|
||||
vm.get_hypervisor(),
|
||||
irq_chip,
|
||||
&mut vcpu,
|
||||
vcpu_init,
|
||||
cpu_id,
|
||||
vcpu_count,
|
||||
has_bios,
|
||||
no_smt,
|
||||
host_cpu_topology,
|
||||
/* enable_pnp_data */ false,
|
||||
/* itmt */ false,
|
||||
force_calibrated_tsc_leaf,
|
||||
)
|
||||
.exit_context(Exit::ConfigureVcpu, "failed to configure vcpu")?;
|
||||
|
||||
#[cfg(feature = "whpx")]
|
||||
Self::whpx_configure_vcpu(&mut vcpu, irq_chip);
|
||||
|
||||
let mut thread_priority_handle = None;
|
||||
if run_rt {
|
||||
// Until we are multi process on Windows, we can't use the normal thread priority APIs;
|
||||
// instead, we use a trick from the audio device which is able to set a thread RT even
|
||||
// though the process itself is not RT.
|
||||
thread_priority_handle = match set_audio_thread_priorities() {
|
||||
Ok(hndl) => Some(hndl),
|
||||
Err(e) => {
|
||||
warn!("Failed to set vcpu thread to real time priority: {}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let vcpu_run_handle = vcpu
|
||||
.take_run_handle(None)
|
||||
.exit_context(Exit::RunnableVcpu, "failed to set thread id for vcpu")?;
|
||||
|
||||
Ok(RunnableVcpuInfo {
|
||||
vcpu,
|
||||
thread_priority_handle,
|
||||
vcpu_run_handle,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run<V>(
|
||||
&self,
|
||||
vcpu: Option<V>,
|
||||
vcpu_init: VcpuInitX86_64,
|
||||
vcpus: Arc<Mutex<Vec<Box<dyn VcpuArch>>>>,
|
||||
vm: impl VmArch + 'static,
|
||||
mut irq_chip: Box<dyn IrqChipArch + 'static>,
|
||||
vcpu_count: usize,
|
||||
run_rt: bool,
|
||||
vcpu_affinity: Option<Vec<usize>>,
|
||||
delay_rt: bool,
|
||||
no_smt: bool,
|
||||
start_barrier: Arc<Barrier>,
|
||||
vcpu_create_barrier: Arc<Barrier>,
|
||||
has_bios: bool,
|
||||
mut io_bus: devices::Bus,
|
||||
mut mmio_bus: devices::Bus,
|
||||
vm_evt_wrtube: SendTube,
|
||||
requires_pvclock_ctrl: bool,
|
||||
run_mode_arc: Arc<VcpuRunMode>,
|
||||
stats: Option<Arc<Mutex<StatisticsCollector>>>,
|
||||
host_cpu_topology: bool,
|
||||
tsc_offset: Option<u64>,
|
||||
force_calibrated_tsc_leaf: bool,
|
||||
) -> Result<JoinHandle<Result<()>>>
|
||||
where
|
||||
V: VcpuArch + 'static,
|
||||
{
|
||||
let context = self.clone();
|
||||
thread::Builder::new()
|
||||
.name(format!("crosvm_vcpu{}", self.cpu_id))
|
||||
.spawn(move || {
|
||||
// Having a closure returning ExitState guarentees that we
|
||||
// send a VmEventType on all code paths after the closure
|
||||
// returns.
|
||||
let vcpu_fn = || -> Result<ExitState> {
|
||||
let runnable_vcpu = Self::runnable_vcpu(
|
||||
context.cpu_id,
|
||||
vcpu,
|
||||
vcpu_init,
|
||||
&vm,
|
||||
irq_chip.as_mut(),
|
||||
vcpu_count,
|
||||
run_rt && !delay_rt,
|
||||
vcpu_affinity,
|
||||
no_smt,
|
||||
has_bios,
|
||||
host_cpu_topology,
|
||||
force_calibrated_tsc_leaf,
|
||||
);
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
let cpuid_context = CpuIdContext::new(
|
||||
context.cpu_id,
|
||||
vcpu_count,
|
||||
no_smt,
|
||||
host_cpu_topology,
|
||||
Some(irq_chip.as_ref()),
|
||||
/* enable_pnp_data */ false,
|
||||
/* itmt */ false,
|
||||
force_calibrated_tsc_leaf,
|
||||
vm.get_hypervisor()
|
||||
.check_capability(HypervisorCap::CalibratedTscLeafRequired),
|
||||
__cpuid_count,
|
||||
__cpuid,
|
||||
);
|
||||
|
||||
// The vcpu_create_barrier is supplied from the main thread in order for it to
|
||||
// wait until this thread is done creating its vcpu.
|
||||
vcpu_create_barrier.wait();
|
||||
|
||||
// Wait for this barrier before continuing forward.
|
||||
start_barrier.wait();
|
||||
|
||||
let RunnableVcpuInfo {
|
||||
vcpu,
|
||||
thread_priority_handle: _thread_priority_handle,
|
||||
vcpu_run_handle,
|
||||
} = runnable_vcpu?;
|
||||
|
||||
if let Some(offset) = tsc_offset {
|
||||
vcpu.set_tsc_offset(offset).unwrap_or_else(|e| {
|
||||
error!(
|
||||
"Failed to set tsc_offset of {} on vcpu {}: {}",
|
||||
offset, context.cpu_id, e
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
// Clone vcpu so it can be used by the main thread to force a vcpu run to exit
|
||||
vcpus
|
||||
.lock()
|
||||
.push(Box::new(vcpu.try_clone().expect("Could not clone vcpu!")));
|
||||
|
||||
mmio_bus.set_access_id(context.cpu_id);
|
||||
io_bus.set_access_id(context.cpu_id);
|
||||
|
||||
vcpu_loop(
|
||||
&context,
|
||||
vcpu,
|
||||
vm,
|
||||
vcpu_run_handle,
|
||||
irq_chip,
|
||||
io_bus,
|
||||
mmio_bus,
|
||||
requires_pvclock_ctrl,
|
||||
run_mode_arc,
|
||||
stats,
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
cpuid_context,
|
||||
)
|
||||
};
|
||||
|
||||
let final_event_data = match vcpu_fn().unwrap_or_else(|e| {
|
||||
error!("vcpu {} run loop exited with error: {}", context.cpu_id, e);
|
||||
ExitState::Stop
|
||||
}) {
|
||||
ExitState::Stop => VmEventType::Exit,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
vm_evt_wrtube
|
||||
.send::<VmEventType>(&final_event_data)
|
||||
.unwrap_or_else(|e| {
|
||||
error!(
|
||||
"failed to send final event {:?} on vcpu {}: {}",
|
||||
final_event_data, context.cpu_id, e
|
||||
)
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
.exit_context(Exit::SpawnVcpu, "failed to spawn VCPU thread")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct VcpuExitData {
|
||||
// Represented by duration since baseline start_instant
|
||||
exit_time: Duration,
|
||||
exit_result: BaseResult<VcpuExit>,
|
||||
}
|
||||
|
||||
impl Display for VcpuExitData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "exit result: {:?}", self.exit_result)
|
||||
}
|
||||
}
|
||||
|
||||
struct VcpuStallMonitor {
|
||||
vcpu_run_threads: Vec<VcpuRunThread>,
|
||||
run_mode: Arc<VcpuRunMode>,
|
||||
}
|
||||
|
||||
impl VcpuStallMonitor {
|
||||
const HOST_STALL_TIMEOUT: Duration = Duration::from_secs(2);
|
||||
const VCPU_CHECKUP_INTERVAL: Duration = Duration::from_secs(1);
|
||||
const STALL_REPORTING_LIMITER: Duration = Duration::from_secs(10);
|
||||
|
||||
pub fn init(run_mode: Arc<VcpuRunMode>) -> VcpuStallMonitor {
|
||||
VcpuStallMonitor {
|
||||
vcpu_run_threads: vec![],
|
||||
run_mode,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_vcpu_thread(&mut self, thread: VcpuRunThread) {
|
||||
self.vcpu_run_threads.push(thread);
|
||||
}
|
||||
|
||||
pub fn run(self, exit_event: &Event) -> Result<JoinHandle<Result<()>>> {
|
||||
let cloned_exit_event = exit_event
|
||||
.try_clone()
|
||||
.exit_context(Exit::CloneEvent, "failed to clone event")?;
|
||||
thread::Builder::new()
|
||||
.name("crosvm_vcpu_stall_monitor".to_string())
|
||||
.spawn(move || {
|
||||
let ex = Executor::new()?;
|
||||
|
||||
let mut timer = TimerAsync::new(Timer::new()?, &ex)?;
|
||||
let mut reset_timer = true;
|
||||
|
||||
let exit_evt_async = EventAsync::new(cloned_exit_event, &ex)?;
|
||||
let exit_future = exit_evt_async.next_val();
|
||||
pin_mut!(exit_future);
|
||||
'main: loop {
|
||||
if reset_timer {
|
||||
timer.reset(
|
||||
Self::VCPU_CHECKUP_INTERVAL,
|
||||
Some(Self::VCPU_CHECKUP_INTERVAL),
|
||||
)?;
|
||||
reset_timer = false;
|
||||
}
|
||||
let timer_future = timer.next_val();
|
||||
pin_mut!(timer_future);
|
||||
match ex.run_until(select2(timer_future, exit_future)) {
|
||||
Ok((timer_result, exit_result)) => {
|
||||
match exit_result {
|
||||
SelectResult::Finished(_) => {
|
||||
info!("vcpu monitor got exit event");
|
||||
break 'main;
|
||||
}
|
||||
SelectResult::Pending(future) => exit_future = future,
|
||||
}
|
||||
|
||||
match timer_result {
|
||||
SelectResult::Finished(Err(e)) => {
|
||||
error!(
|
||||
"vcpu monitor aborting due to error awaiting future: {}",
|
||||
e
|
||||
);
|
||||
break 'main;
|
||||
}
|
||||
SelectResult::Finished(_) => self.report_any_stalls(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("vcpu monitor failed to wait on future set: {:?}", e);
|
||||
break 'main;
|
||||
}
|
||||
}
|
||||
|
||||
// Always ensure the vcpus aren't suspended before continuing to montior.
|
||||
let mut run_mode_lock = self.run_mode.mtx.lock();
|
||||
loop {
|
||||
match *run_mode_lock {
|
||||
VmRunMode::Running => break,
|
||||
VmRunMode::Suspending | VmRunMode::Breakpoint => {
|
||||
info!("vcpu monitor pausing until end of suspension");
|
||||
run_mode_lock = self.run_mode.cvar.wait(run_mode_lock);
|
||||
reset_timer = true;
|
||||
}
|
||||
VmRunMode::Exiting => {
|
||||
info!("vcpu monitor detected vm exit");
|
||||
break 'main;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.exit_context(
|
||||
Exit::SpawnVcpuMonitor,
|
||||
"failed to spawn VCPU stall monitor thread",
|
||||
)
|
||||
}
|
||||
|
||||
fn report_any_stalls(&self) {
|
||||
// TODO(b/208267651): Add and fire Clearcut events for stalls (and add tests)
|
||||
// TODO(b/208267651): Also test guest stalls (vcpu.run() goes too long without exiting)
|
||||
let now = Instant::now();
|
||||
for vcpu_thread in self.vcpu_run_threads.iter() {
|
||||
let monitoring_metadata = vcpu_thread.monitoring_metadata.as_ref().unwrap();
|
||||
if let Some(ref exit_snapshot) = monitoring_metadata.last_exit_snapshot.lock().clone() {
|
||||
let last_run =
|
||||
Duration::from_millis(monitoring_metadata.last_run_time.load(Ordering::SeqCst));
|
||||
if last_run < exit_snapshot.exit_time {
|
||||
// VCPU is between runs
|
||||
let time_since_exit = now.saturating_duration_since(
|
||||
monitoring_metadata.start_instant + exit_snapshot.exit_time,
|
||||
);
|
||||
if time_since_exit > Self::HOST_STALL_TIMEOUT {
|
||||
self.report_stall(vcpu_thread.cpu_id, exit_snapshot, time_since_exit);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn report_stall(&self, cpu_id: usize, exit_data: &VcpuExitData, stall_time: Duration) {
|
||||
if stall_time > Self::STALL_REPORTING_LIMITER {
|
||||
return;
|
||||
}
|
||||
// Double check the Vm is running. We don't care about stalls during suspension/exit
|
||||
if *self.run_mode.mtx.lock() != VmRunMode::Running {
|
||||
let duration_string = format!("{:.1}sec", stall_time.as_secs_f32());
|
||||
error!(
|
||||
"Host stall for {} on VCPU {} exit while handling: {}",
|
||||
duration_string, cpu_id, exit_data,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_vcpu_signal_handler() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_all_vcpus<V: VmArch + 'static, Vcpu: VcpuArch + 'static>(
|
||||
vcpus: Vec<Option<Vcpu>>,
|
||||
vcpu_boxes: Arc<Mutex<Vec<Box<dyn VcpuArch>>>>,
|
||||
guest_os: &RunnableLinuxVm<V, Vcpu>,
|
||||
exit_evt: &Event,
|
||||
vm_evt_wrtube: &SendTube,
|
||||
pvclock_host_tube: &Option<Tube>,
|
||||
stats: &Option<Arc<Mutex<StatisticsCollector>>>,
|
||||
host_cpu_topology: bool,
|
||||
run_mode_arc: Arc<VcpuRunMode>,
|
||||
tsc_sync_mitigations: TscSyncMitigations,
|
||||
force_calibrated_tsc_leaf: bool,
|
||||
) -> Result<Vec<JoinHandle<Result<()>>>> {
|
||||
let mut vcpu_threads = Vec::with_capacity(guest_os.vcpu_count + 1);
|
||||
let start_barrier = Arc::new(Barrier::new(guest_os.vcpu_count + 1));
|
||||
let enable_vcpu_monitoring = anti_tamper::enable_vcpu_monitoring();
|
||||
setup_vcpu_signal_handler()?;
|
||||
|
||||
let mut stall_monitor =
|
||||
enable_vcpu_monitoring.then(|| VcpuStallMonitor::init(run_mode_arc.clone()));
|
||||
for (cpu_id, vcpu) in vcpus.into_iter().enumerate() {
|
||||
let vcpu_affinity = match guest_os.vcpu_affinity.clone() {
|
||||
Some(VcpuAffinity::Global(v)) => Some(v),
|
||||
Some(VcpuAffinity::PerVcpu(mut m)) => Some(m.remove(&cpu_id).unwrap_or_default()),
|
||||
None => None,
|
||||
};
|
||||
|
||||
// TSC sync mitigations may set vcpu affinity and set a TSC offset
|
||||
let (vcpu_affinity, tsc_offset): (Option<Vec<usize>>, Option<u64>) =
|
||||
if let Some(mitigation_affinity) = tsc_sync_mitigations.get_vcpu_affinity(cpu_id) {
|
||||
if vcpu_affinity.is_none() {
|
||||
(
|
||||
Some(mitigation_affinity),
|
||||
tsc_sync_mitigations.get_vcpu_tsc_offset(cpu_id),
|
||||
)
|
||||
} else {
|
||||
error!(
|
||||
"Core affinity {:?} specified via commandline conflicts and overrides \
|
||||
affinity needed for TSC sync mitigation: {:?}.",
|
||||
vcpu_affinity, mitigation_affinity
|
||||
);
|
||||
(vcpu_affinity, None)
|
||||
}
|
||||
} else {
|
||||
(vcpu_affinity, None)
|
||||
};
|
||||
|
||||
let vcpu_init = &guest_os.vcpu_init[cpu_id];
|
||||
// The vcpu_create_barrier allows the main thread to delay the spawning of additional
|
||||
// vcpu threads until a single vcpu thread spawned has finished creating it's vcpu.
|
||||
// We currently use this to allow creation of 1 vcpu at a time for all hypervisors.
|
||||
// There are issues with multiple hypervisors with this approach:
|
||||
// - Windows 11 has a regression which causes a BSOD with creation of multiple vcpu
|
||||
// in parallel. http://b/229635845 for more details.
|
||||
// - GHAXM/HAXM cannot create vcpu0 in parallel with other Vcpus.
|
||||
let vcpu_create_barrier = Arc::new(Barrier::new(2));
|
||||
let vcpu_run_thread = VcpuRunThread::new(cpu_id, enable_vcpu_monitoring);
|
||||
let join_handle = vcpu_run_thread.run(
|
||||
vcpu,
|
||||
vcpu_init.clone(),
|
||||
vcpu_boxes.clone(),
|
||||
guest_os
|
||||
.vm
|
||||
.try_clone()
|
||||
.exit_context(Exit::CloneEvent, "failed to clone vm")?,
|
||||
guest_os
|
||||
.irq_chip
|
||||
.try_box_clone()
|
||||
.exit_context(Exit::CloneEvent, "failed to clone event")?,
|
||||
guest_os.vcpu_count,
|
||||
guest_os.rt_cpus.contains(&cpu_id),
|
||||
vcpu_affinity,
|
||||
guest_os.delay_rt,
|
||||
guest_os.no_smt,
|
||||
start_barrier.clone(),
|
||||
vcpu_create_barrier.clone(),
|
||||
guest_os.has_bios,
|
||||
(*guest_os.io_bus).clone(),
|
||||
(*guest_os.mmio_bus).clone(),
|
||||
vm_evt_wrtube
|
||||
.try_clone()
|
||||
.exit_context(Exit::CloneTube, "failed to clone tube")?,
|
||||
pvclock_host_tube.is_none(),
|
||||
run_mode_arc.clone(),
|
||||
stats.clone(),
|
||||
host_cpu_topology,
|
||||
tsc_offset,
|
||||
force_calibrated_tsc_leaf,
|
||||
)?;
|
||||
if let Some(ref mut monitor) = stall_monitor {
|
||||
monitor.add_vcpu_thread(vcpu_run_thread);
|
||||
}
|
||||
|
||||
// Wait until the vcpu is created before we start a new vcpu thread
|
||||
vcpu_create_barrier.wait();
|
||||
|
||||
vcpu_threads.push(join_handle);
|
||||
}
|
||||
if let Some(monitor) = stall_monitor {
|
||||
vcpu_threads.push(monitor.run(exit_evt)?);
|
||||
}
|
||||
// Now wait on the start barrier to start all threads at the same time.
|
||||
start_barrier.wait();
|
||||
Ok(vcpu_threads)
|
||||
}
|
||||
|
||||
fn vcpu_loop<V>(
|
||||
context: &VcpuRunThread,
|
||||
mut vcpu: V,
|
||||
vm: impl VmArch + 'static,
|
||||
vcpu_run_handle: VcpuRunHandle,
|
||||
irq_chip: Box<dyn IrqChipArch + 'static>,
|
||||
mut io_bus: Bus,
|
||||
mut mmio_bus: Bus,
|
||||
requires_pvclock_ctrl: bool,
|
||||
run_mode_arc: Arc<VcpuRunMode>,
|
||||
stats: Option<Arc<Mutex<StatisticsCollector>>>,
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] cpuid_context: CpuIdContext,
|
||||
) -> Result<ExitState>
|
||||
where
|
||||
V: VcpuArch + 'static,
|
||||
{
|
||||
let mut exit_stats = VmExitStatistics::new();
|
||||
mmio_bus.stats.set_enabled(stats.is_some());
|
||||
io_bus.stats.set_enabled(stats.is_some());
|
||||
exit_stats.set_enabled(stats.is_some());
|
||||
|
||||
let mut save_tsc_offset = true;
|
||||
|
||||
loop {
|
||||
let _trace_event = trace_event!(crosvm, "vcpu loop");
|
||||
let mut check_vm_shutdown = false;
|
||||
|
||||
match irq_chip.wait_until_runnable(&vcpu).with_exit_context(
|
||||
Exit::WaitUntilRunnable,
|
||||
|| {
|
||||
format!(
|
||||
"error waiting for vcpu {} to become runnable",
|
||||
context.cpu_id
|
||||
)
|
||||
},
|
||||
)? {
|
||||
VcpuRunState::Runnable => {}
|
||||
VcpuRunState::Interrupted => check_vm_shutdown = true,
|
||||
}
|
||||
|
||||
if !check_vm_shutdown {
|
||||
let exit = {
|
||||
let _trace_event = trace_event!(crosvm, "vcpu::run");
|
||||
if let Some(ref monitoring_metadata) = context.monitoring_metadata {
|
||||
monitoring_metadata.last_run_time.store(
|
||||
// Safe conversion because millis will always be < u32::MAX
|
||||
monitoring_metadata
|
||||
.start_instant
|
||||
.elapsed()
|
||||
.as_millis()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
}
|
||||
vcpu.run(&vcpu_run_handle)
|
||||
};
|
||||
if let Some(ref monitoring_metadata) = context.monitoring_metadata {
|
||||
*monitoring_metadata.last_exit_snapshot.lock() = Some(VcpuExitData {
|
||||
exit_time: monitoring_metadata.start_instant.elapsed(),
|
||||
exit_result: exit,
|
||||
});
|
||||
}
|
||||
|
||||
// save the tsc offset if we need to
|
||||
if save_tsc_offset {
|
||||
if let Ok(offset) = vcpu.get_tsc_offset() {
|
||||
save_vcpu_tsc_offset(offset, context.cpu_id);
|
||||
} else {
|
||||
error!("Unable to determine TSC offset");
|
||||
}
|
||||
save_tsc_offset = false;
|
||||
}
|
||||
|
||||
let start = exit_stats.start_stat();
|
||||
|
||||
match exit {
|
||||
Ok(VcpuExit::Io) => {
|
||||
let _trace_event = trace_event!(crosvm, "VcpuExit::Io");
|
||||
vcpu.handle_io(&mut |IoParams { address, mut size, operation}| {
|
||||
match operation {
|
||||
IoOperation::Read => {
|
||||
let mut data = [0u8; 8];
|
||||
if size > data.len() {
|
||||
error!("unsupported IoIn size of {} bytes", size);
|
||||
size = data.len();
|
||||
}
|
||||
io_bus.read(address, &mut data[..size]);
|
||||
Some(data)
|
||||
}
|
||||
IoOperation::Write { data } => {
|
||||
if size > data.len() {
|
||||
error!("unsupported IoOut size of {} bytes", size);
|
||||
size = data.len()
|
||||
}
|
||||
vm.handle_io_events(IoEventAddress::Pio(address), &data[..size])
|
||||
.unwrap_or_else(|e| error!(
|
||||
"failed to handle ioevent for pio write to {} on vcpu {}: {}",
|
||||
address, context.cpu_id, e
|
||||
));
|
||||
io_bus.write(address, &data[..size]);
|
||||
None
|
||||
}
|
||||
}
|
||||
}).unwrap_or_else(|e| error!("failed to handle io: {}", e));
|
||||
}
|
||||
Ok(VcpuExit::Mmio) => {
|
||||
let _trace_event = trace_event!(crosvm, "VcpuExit::Mmio");
|
||||
vcpu.handle_mmio(&mut |IoParams { address, mut size, operation }| {
|
||||
match operation {
|
||||
IoOperation::Read => {
|
||||
let mut data = [0u8; 8];
|
||||
if size > data.len() {
|
||||
error!("unsupported MmioRead size of {} bytes", size);
|
||||
size = data.len();
|
||||
}
|
||||
{
|
||||
let data = &mut data[..size];
|
||||
if !mmio_bus.read(address, data) {
|
||||
info!(
|
||||
"mmio read failed: {:x}; trying memory read..",
|
||||
address
|
||||
);
|
||||
vm.get_memory()
|
||||
.read_exact_at_addr(
|
||||
data,
|
||||
vm_memory::GuestAddress(address),
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
error!(
|
||||
"guest memory read failed at {:x}: {}",
|
||||
address, e
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(data)
|
||||
}
|
||||
IoOperation::Write { data } => {
|
||||
if size > data.len() {
|
||||
error!("unsupported MmioWrite size of {} bytes", size);
|
||||
size = data.len()
|
||||
}
|
||||
let data = &data[..size];
|
||||
vm.handle_io_events(IoEventAddress::Mmio(address), data)
|
||||
.unwrap_or_else(|e| error!(
|
||||
"failed to handle ioevent for mmio write to {} on vcpu {}: {}",
|
||||
address, context.cpu_id, e
|
||||
));
|
||||
if !mmio_bus.write(address, data) {
|
||||
info!(
|
||||
"mmio write failed: {:x}; trying memory write..",
|
||||
address
|
||||
);
|
||||
vm.get_memory()
|
||||
.write_all_at_addr(data, vm_memory::GuestAddress(address))
|
||||
.unwrap_or_else(|e| error!(
|
||||
"guest memory write failed at {:x}: {}",
|
||||
address, e
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
}).unwrap_or_else(|e| error!("failed to handle mmio: {}", e));
|
||||
}
|
||||
Ok(VcpuExit::IoapicEoi { vector }) => {
|
||||
irq_chip.broadcast_eoi(vector).unwrap_or_else(|e| {
|
||||
error!(
|
||||
"failed to broadcast eoi {} on vcpu {}: {}",
|
||||
vector, context.cpu_id, e
|
||||
)
|
||||
});
|
||||
}
|
||||
Ok(VcpuExit::IrqWindowOpen) => {}
|
||||
Ok(VcpuExit::Hlt) => irq_chip.halted(context.cpu_id),
|
||||
|
||||
// VcpuExit::Shutdown is always an error on Windows. HAXM exits with
|
||||
// Shutdown only for triple faults and other vcpu panics. WHPX never exits
|
||||
// with Shutdown. Normal reboots and shutdowns, like window close, use
|
||||
// the vm event tube and VmRunMode::Exiting instead of VcpuExit::Shutdown.
|
||||
Ok(VcpuExit::Shutdown) => bail_exit_code!(Exit::VcpuShutdown, "vcpu shutdown"),
|
||||
Ok(VcpuExit::FailEntry {
|
||||
hardware_entry_failure_reason,
|
||||
}) => bail_exit_code!(
|
||||
Exit::VcpuFailEntry,
|
||||
"vcpu hw run failure: {:#x}",
|
||||
hardware_entry_failure_reason,
|
||||
),
|
||||
Ok(VcpuExit::SystemEventShutdown) => {
|
||||
bail_exit_code!(Exit::VcpuSystemEvent, "vcpu SystemEventShutdown")
|
||||
}
|
||||
Ok(VcpuExit::SystemEventReset) => {
|
||||
bail_exit_code!(Exit::VcpuSystemEvent, "vcpu SystemEventReset")
|
||||
}
|
||||
Ok(VcpuExit::SystemEventCrash) => {
|
||||
bail_exit_code!(Exit::VcpuSystemEvent, "vcpu SystemEventCrash")
|
||||
}
|
||||
|
||||
// When we're shutting down (e.g., emulator window gets closed), GVM vmexits
|
||||
// with KVM_EXIT_INTR, which vcpu.run maps to VcpuExit::Intr. But KVM_EXIT_INTR
|
||||
// can happen during normal operation too, when GVM's timer finds requests
|
||||
// pending from the host. So we set check_vm_shutdown, then below check the
|
||||
// VmRunMode state to see if we should exit the run loop.
|
||||
Ok(VcpuExit::Intr) => check_vm_shutdown = true,
|
||||
Ok(VcpuExit::Canceled) => check_vm_shutdown = true,
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
Ok(VcpuExit::Cpuid { mut entry }) => {
|
||||
let _trace_event = trace_event!(crosvm, "VcpuExit::Cpuid");
|
||||
// adjust the results based on crosvm logic
|
||||
adjust_cpuid(&mut entry, &cpuid_context);
|
||||
|
||||
// let the vcpu finish handling the exit
|
||||
vcpu.handle_cpuid(&entry).unwrap_or_else(|e| {
|
||||
error!(
|
||||
"failed to handle setting cpuid results on cpu {}: {}",
|
||||
context.cpu_id, e
|
||||
)
|
||||
});
|
||||
}
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
Ok(VcpuExit::MsrAccess) => {} // MsrAccess handled by hypervisor impl
|
||||
Ok(r) => {
|
||||
error!("unexpected vcpu.run return value: {:?}", r);
|
||||
check_vm_shutdown = true;
|
||||
}
|
||||
Err(e) => match e.errno() {
|
||||
ERROR_RETRY_I32 => {}
|
||||
_ => {
|
||||
run_mode_arc.set_and_notify(VmRunMode::Exiting);
|
||||
Err(e).exit_context(Exit::VcpuRunError, "vcpu run error")?;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
exit_stats.end_stat(&exit, start);
|
||||
}
|
||||
|
||||
if check_vm_shutdown {
|
||||
let mut run_mode_lock = run_mode_arc.mtx.lock();
|
||||
loop {
|
||||
match *run_mode_lock {
|
||||
VmRunMode::Running => break,
|
||||
VmRunMode::Suspending => {
|
||||
// On KVM implementations that use a paravirtualized clock (e.g.
|
||||
// x86), a flag must be set to indicate to the guest kernel that
|
||||
// a VCPU was suspended. The guest kernel will use this flag to
|
||||
// prevent the soft lockup detection from triggering when this
|
||||
// VCPU resumes, which could happen days later in realtime.
|
||||
if requires_pvclock_ctrl {
|
||||
vcpu.pvclock_ctrl().unwrap_or_else(|e| error!(
|
||||
"failed to signal to hypervisor that vcpu {} is being suspended: {}",
|
||||
context.cpu_id, e
|
||||
));
|
||||
}
|
||||
}
|
||||
VmRunMode::Breakpoint => {}
|
||||
VmRunMode::Exiting => {
|
||||
if let Some(stats) = stats {
|
||||
let mut collector = stats.lock();
|
||||
collector.pio_bus_stats.push(io_bus.stats);
|
||||
collector.mmio_bus_stats.push(mmio_bus.stats);
|
||||
collector.vm_exit_stats.push(exit_stats);
|
||||
}
|
||||
return Ok(ExitState::Stop);
|
||||
}
|
||||
}
|
||||
// Give ownership of our exclusive lock to the condition variable that
|
||||
// will block. When the condition variable is notified, `wait` will
|
||||
// unblock and return a new exclusive lock.
|
||||
run_mode_lock = run_mode_arc.cvar.wait(run_mode_lock);
|
||||
}
|
||||
}
|
||||
|
||||
irq_chip.inject_interrupts(&vcpu).unwrap_or_else(|e| {
|
||||
error!(
|
||||
"failed to inject interrupts for vcpu {}: {}",
|
||||
context.cpu_id, e
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
struct SetupData {
|
||||
pub monitor: VcpuStallMonitor,
|
||||
pub exit_evt: Event,
|
||||
}
|
||||
|
||||
fn set_up_stall_monitor(vcpu_count: usize) -> Result<SetupData> {
|
||||
let run_mode = Arc::new(VcpuRunMode::default());
|
||||
let mut monitor = VcpuStallMonitor::init(run_mode.clone());
|
||||
|
||||
for id in 0..vcpu_count {
|
||||
let new_vcpu = VcpuRunThread::new(id, true /* enable_vcpu_monitoring */);
|
||||
monitor.add_vcpu_thread(new_vcpu);
|
||||
}
|
||||
|
||||
Ok(SetupData {
|
||||
monitor,
|
||||
exit_evt: Event::new().expect("Failed to create event"),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stall_monitor_closes_on_exit_evt() -> Result<()> {
|
||||
let SetupData { monitor, exit_evt } = set_up_stall_monitor(1)?;
|
||||
|
||||
let _ = exit_evt.write(1)?;
|
||||
let _ = monitor
|
||||
.run(&exit_evt)?
|
||||
.join()
|
||||
.unwrap_or_else(|e| panic!("Thread join failed: {:?}", e));
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue