use std::cell::Cell;
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::io;
use std::result;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use super::super::{FC_EXIT_CODE_GENERIC_ERROR, FC_EXIT_CODE_OK};
use crate::vmm_config::machine_config::CpuFeaturesTemplate;
use arch::ArchMemoryInfo;
use crossbeam_channel::{unbounded, Receiver, RecvTimeoutError, Sender};
use devices::legacy::VcpuList;
use hvf::{HvfVcpu, HvfVm, VcpuExit, Vcpus};
use utils::eventfd::EventFd;
use vm_memory::{
Address, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap, GuestMemoryRegion,
};
#[derive(Debug)]
pub enum Error {
GuestMemoryMmap(GuestMemoryError),
NotEnoughMemorySlots,
REGSConfiguration(arch::aarch64::regs::Error),
SetUserMemoryRegion(hvf::Error),
SignalVcpu(utils::errno::Error),
VcpuArmInit,
VcpuArmPreferredTarget,
VcpuCountNotInitialized,
VcpuRun,
VcpuSpawn(io::Error),
VcpuTlsInit,
VcpuTlsNotPresent,
VcpuUnhandledKvmExit,
VmSetup(hvf::Error),
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
use self::Error::*;
match self {
GuestMemoryMmap(e) => write!(f, "Guest memory error: {e:?}"),
VcpuCountNotInitialized => write!(f, "vCPU count is not initialized"),
VmSetup(e) => write!(f, "Cannot configure the microvm: {e:?}"),
VcpuRun => write!(f, "Cannot run the VCPUs"),
NotEnoughMemorySlots => write!(
f,
"The number of configured slots is bigger than the maximum reported by KVM"
),
SetUserMemoryRegion(e) => write!(f, "Cannot set the memory regions: {e:?}"),
SignalVcpu(e) => write!(f, "Failed to signal Vcpu: {e}"),
REGSConfiguration(e) => write!(
f,
"Error configuring the general purpose aarch64 registers: {e:?}"
),
VcpuSpawn(e) => write!(f, "Cannot spawn a new vCPU thread: {e}"),
VcpuTlsInit => write!(f, "Cannot clean init vcpu TLS"),
VcpuTlsNotPresent => write!(f, "Vcpu not present in TLS"),
VcpuUnhandledKvmExit => write!(f, "Unexpected KVM_RUN exit reason"),
VcpuArmPreferredTarget => write!(f, "Error getting the Vcpu preferred target on Arm"),
VcpuArmInit => write!(f, "Error doing Vcpu Init on Arm"),
}
}
}
pub type Result<T> = result::Result<T, Error>;
pub struct Vm {
hvf_vm: HvfVm,
}
impl Vm {
pub fn new(nested_enabled: bool) -> Result<Self> {
let hvf_vm = HvfVm::new(nested_enabled).map_err(Error::VmSetup)?;
Ok(Vm { hvf_vm })
}
pub fn hvf_vm(&self) -> &HvfVm {
&self.hvf_vm
}
pub fn memory_init(&mut self, guest_mem: &GuestMemoryMmap) -> Result<()> {
for region in guest_mem.iter() {
let host_addr = guest_mem.get_host_address(region.start_addr()).unwrap();
debug!(
"Guest memory host_addr={:x?} guest_addr={:x?} len={:x?}",
host_addr,
region.start_addr().raw_value(),
region.len()
);
self.hvf_vm
.map_memory(
host_addr as u64,
region.start_addr().raw_value(),
region.len(),
)
.map_err(Error::SetUserMemoryRegion)?;
}
Ok(())
}
pub fn add_mapping(
&self,
reply_sender: Sender<bool>,
host_addr: u64,
guest_addr: u64,
len: u64,
) {
debug!("add_mapping: host_addr={host_addr:x}, guest_addr={guest_addr:x}, len={len}");
if let Err(e) = self.hvf_vm.unmap_memory(guest_addr, len) {
error!("Error removing memory map: {e:?}");
}
if let Err(e) = self.hvf_vm.map_memory(host_addr, guest_addr, len) {
error!("Error adding memory map: {e:?}");
reply_sender.send(false).unwrap();
} else {
reply_sender.send(true).unwrap();
}
}
pub fn remove_mapping(&self, reply_sender: Sender<bool>, guest_addr: u64, len: u64) {
debug!("remove_mapping: guest_addr={guest_addr:x}, len={len}");
if let Err(e) = self.hvf_vm.unmap_memory(guest_addr, len) {
error!("Error removing memory map: {e:?}");
reply_sender.send(false).unwrap();
} else {
reply_sender.send(true).unwrap();
}
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct VcpuConfig {
pub vcpu_count: u8,
pub ht_enabled: bool,
pub cpu_template: Option<CpuFeaturesTemplate>,
}
type VcpuCell = Cell<Option<*const Vcpu>>;
pub struct Vcpu {
id: u8,
boot_entry_addr: u64,
boot_receiver: Option<Receiver<u64>>,
boot_senders: Option<HashMap<u64, Sender<u64>>>,
fdt_addr: u64,
mmio_bus: Option<devices::Bus>,
#[cfg_attr(all(test, target_arch = "aarch64"), allow(unused))]
exit_evt: EventFd,
#[cfg(target_arch = "aarch64")]
mpidr: u64,
#[allow(unused)]
event_receiver: Receiver<VcpuEvent>,
event_sender: Option<Sender<VcpuEvent>>,
response_receiver: Option<Receiver<VcpuResponse>>,
response_sender: Sender<VcpuResponse>,
vcpu_list: Arc<VcpuList>,
nested_enabled: bool,
}
impl Vcpu {
thread_local!(static TLS_VCPU_PTR: VcpuCell = const { Cell::new(None) });
fn init_thread_local_data(&mut self) -> Result<()> {
Self::TLS_VCPU_PTR.with(|cell: &VcpuCell| {
if cell.get().is_some() {
return Err(Error::VcpuTlsInit);
}
cell.set(Some(self as *const Vcpu));
Ok(())
})
}
fn reset_thread_local_data(&mut self) -> Result<()> {
Self::TLS_VCPU_PTR.with(|cell: &VcpuCell| {
if let Some(vcpu_ptr) = cell.get() {
if std::ptr::eq(vcpu_ptr, self) {
Self::TLS_VCPU_PTR.with(|cell: &VcpuCell| cell.take());
return Ok(());
}
}
Err(Error::VcpuTlsNotPresent)
})
}
pub fn register_kick_signal_handler() {
}
pub fn new_aarch64(
id: u8,
boot_entry_addr: GuestAddress,
boot_receiver: Option<Receiver<u64>>,
exit_evt: EventFd,
vcpu_list: Arc<VcpuList>,
nested_enabled: bool,
) -> Result<Self> {
let (event_sender, event_receiver) = unbounded();
let (response_sender, response_receiver) = unbounded();
Ok(Vcpu {
id,
boot_entry_addr: boot_entry_addr.raw_value(),
boot_receiver,
boot_senders: None,
fdt_addr: 0,
mmio_bus: None,
exit_evt,
mpidr: id as u64,
event_receiver,
event_sender: Some(event_sender),
response_receiver: Some(response_receiver),
response_sender,
vcpu_list,
nested_enabled,
})
}
pub fn cpu_index(&self) -> u8 {
self.id
}
pub fn get_mpidr(&self) -> u64 {
self.mpidr
}
pub fn set_mmio_bus(&mut self, mmio_bus: devices::Bus) {
self.mmio_bus = Some(mmio_bus);
}
pub fn set_boot_senders(&mut self, boot_senders: HashMap<u64, Sender<u64>>) {
self.boot_senders = Some(boot_senders);
}
pub fn configure_aarch64(&mut self, mem_info: &ArchMemoryInfo) -> Result<()> {
self.fdt_addr = mem_info.fdt_addr;
Ok(())
}
pub fn start_threaded(mut self) -> Result<VcpuHandle> {
let event_sender = self.event_sender.take().unwrap();
let response_receiver = self.response_receiver.take().unwrap();
let (init_tls_sender, init_tls_receiver) = unbounded();
let vcpu_thread = thread::Builder::new()
.name(format!("fc_vcpu {}", self.cpu_index()))
.spawn(move || {
self.init_thread_local_data()
.expect("Cannot cleanly initialize vcpu TLS.");
self.run(init_tls_sender);
})
.map_err(Error::VcpuSpawn)?;
init_tls_receiver
.recv()
.expect("Error waiting for TLS initialization.");
Ok(VcpuHandle::new(
event_sender,
response_receiver,
vcpu_thread,
))
}
fn run_emulation(&mut self, hvf_vcpu: &mut HvfVcpu) -> Result<VcpuEmulation> {
let vcpuid = hvf_vcpu.id();
match hvf_vcpu.run(self.vcpu_list.clone()) {
Ok(exit) => match exit {
VcpuExit::Breakpoint => {
debug!("vCPU {vcpuid} breakpoint");
Ok(VcpuEmulation::Interrupted)
}
VcpuExit::Canceled => {
debug!("vCPU {vcpuid} canceled");
Ok(VcpuEmulation::Handled)
}
VcpuExit::CpuOn(mpidr, entry, context_id) => {
debug!("CpuOn: mpidr=0x{mpidr:x} entry=0x{entry:x} context_id={context_id}");
if let Some(boot_senders) = &self.boot_senders {
if let Some(sender) = boot_senders.get(&mpidr) {
sender.send(entry).unwrap()
}
} else {
error!("CpuOn request coming from an unexpected vCPU={}", self.id);
}
Ok(VcpuEmulation::Handled)
}
VcpuExit::HypervisorCall => {
debug!("vCPU {vcpuid} HVC");
Ok(VcpuEmulation::Handled)
}
VcpuExit::MmioRead(addr, data) => {
if let Some(ref mmio_bus) = self.mmio_bus {
debug!("vCPU {vcpuid} MMIO read 0x{addr:x}");
mmio_bus.read(vcpuid, addr, data);
}
Ok(VcpuEmulation::Handled)
}
VcpuExit::MmioWrite(addr, data) => {
if let Some(ref mmio_bus) = self.mmio_bus {
mmio_bus.write(vcpuid, addr, data);
}
Ok(VcpuEmulation::Handled)
}
VcpuExit::PsciHandled => {
debug!("vCPU {vcpuid} PSCI");
Ok(VcpuEmulation::Handled)
}
VcpuExit::SecureMonitorCall => {
debug!("vCPU {vcpuid} SMC");
Ok(VcpuEmulation::Handled)
}
VcpuExit::Shutdown => {
info!("vCPU {vcpuid} received shutdown signal");
Ok(VcpuEmulation::Stopped)
}
VcpuExit::SystemRegister => {
debug!("vCPU {vcpuid} accessed a system register");
Ok(VcpuEmulation::Handled)
}
VcpuExit::VtimerActivated => {
debug!("vCPU {vcpuid} VtimerActivated");
self.vcpu_list.set_vtimer_irq(vcpuid);
Ok(VcpuEmulation::Handled)
}
VcpuExit::WaitForEvent => {
debug!("vCPU {vcpuid} WaitForEvent");
Ok(VcpuEmulation::WaitForEvent)
}
VcpuExit::WaitForEventExpired => {
debug!("vCPU {vcpuid} WaitForEventExpired");
Ok(VcpuEmulation::WaitForEventExpired)
}
VcpuExit::WaitForEventTimeout(duration) => {
debug!("vCPU {vcpuid} WaitForEventTimeout timeout={duration:?}");
Ok(VcpuEmulation::WaitForEventTimeout(duration))
}
},
Err(e) => panic!("Error running HVF vCPU: {e:?}"),
}
}
pub fn run(&mut self, init_tls_sender: Sender<bool>) {
let mut hvf_vcpu =
HvfVcpu::new(self.mpidr, self.nested_enabled).expect("Can't create HVF vCPU");
let hvf_vcpuid = hvf_vcpu.id();
init_tls_sender
.send(true)
.expect("Cannot notify vcpu TLS initialization.");
let (wfe_sender, wfe_receiver) = unbounded();
self.vcpu_list.register(hvf_vcpuid, wfe_sender);
let entry_addr = if let Some(boot_receiver) = &self.boot_receiver {
boot_receiver.recv().unwrap()
} else {
self.boot_entry_addr
};
hvf_vcpu
.set_initial_state(entry_addr, self.fdt_addr)
.unwrap_or_else(|_| panic!("Can't set HVF vCPU {hvf_vcpuid} initial state"));
loop {
match self.run_emulation(&mut hvf_vcpu) {
Ok(VcpuEmulation::Handled) => (),
Ok(VcpuEmulation::Interrupted) => self.wait_for_resume(),
Ok(VcpuEmulation::WaitForEvent) => {
self.wait_for_event(hvf_vcpuid, &wfe_receiver, None)
}
Ok(VcpuEmulation::WaitForEventExpired) => (),
Ok(VcpuEmulation::WaitForEventTimeout(timeout)) => {
self.wait_for_event(hvf_vcpuid, &wfe_receiver, Some(timeout))
}
Ok(VcpuEmulation::Stopped) => {
self.exit(FC_EXIT_CODE_OK);
break;
}
Err(_) => {
self.exit(FC_EXIT_CODE_GENERIC_ERROR);
break;
}
}
}
}
fn wait_for_event(
&mut self,
hvf_vcpuid: u64,
receiver: &Receiver<u32>,
timeout: Option<Duration>,
) {
if self.vcpu_list.should_wait(hvf_vcpuid) {
if let Some(timeout) = timeout {
match receiver.recv_timeout(timeout) {
Ok(_) => {}
Err(e) => match e {
RecvTimeoutError::Timeout => {}
RecvTimeoutError::Disconnected => panic!("WFE channel closed unexpectedly"),
},
}
} else {
receiver.recv().unwrap();
}
}
}
fn wait_for_resume(&mut self) {}
fn exit(&mut self, exit_code: u8) {
self.response_sender
.send(VcpuResponse::Exited(exit_code))
.expect("failed to send Exited status");
if let Err(e) = self.exit_evt.write(1) {
error!("Failed signaling vcpu exit event: {e}");
}
}
}
impl Drop for Vcpu {
fn drop(&mut self) {
let _ = self.reset_thread_local_data();
}
}
#[allow(unused)]
#[derive(Debug)]
pub enum VcpuEvent {
Pause,
Resume,
}
#[derive(Debug, Eq, PartialEq)]
pub enum VcpuResponse {
Paused,
Resumed,
Exited(u8),
}
pub struct VcpuHandle {
event_sender: Sender<VcpuEvent>,
response_receiver: Receiver<VcpuResponse>,
}
impl VcpuHandle {
pub fn new(
event_sender: Sender<VcpuEvent>,
response_receiver: Receiver<VcpuResponse>,
_vcpu_thread: thread::JoinHandle<()>,
) -> Self {
Self {
event_sender,
response_receiver,
}
}
pub fn send_event(&self, event: VcpuEvent) -> Result<()> {
self.event_sender
.send(event)
.expect("event sender channel closed on vcpu end.");
Ok(())
}
pub fn response_receiver(&self) -> &Receiver<VcpuResponse> {
&self.response_receiver
}
}
enum VcpuEmulation {
Handled,
Interrupted,
Stopped,
WaitForEvent,
WaitForEventExpired,
WaitForEventTimeout(Duration),
}
#[cfg(test)]
mod tests {
#[cfg(target_arch = "x86_64")]
use crossbeam_channel::RecvTimeoutError;
use std::sync::Arc;
#[cfg(target_arch = "x86_64")]
use std::time::Duration;
use super::*;
use arch::aarch64::layout::DRAM_MEM_START_EFI;
use devices::legacy::VcpuList;
use vm_memory::{GuestAddress, GuestMemoryMmap};
fn setup_vcpu(mem_size: usize) -> (Vcpu, GuestMemoryMmap) {
let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), mem_size)]).unwrap();
let exit_evt = EventFd::new(utils::eventfd::EFD_NONBLOCK).unwrap();
let vcpu_list = Arc::new(VcpuList::new(1));
let vcpu = Vcpu::new_aarch64(1, GuestAddress(0), None, exit_evt, vcpu_list, false).unwrap();
(vcpu, gm)
}
#[test]
fn test_set_mmio_bus() {
let (mut vcpu, _) = setup_vcpu(0x1000);
assert!(vcpu.mmio_bus.is_none());
vcpu.set_mmio_bus(devices::Bus::new());
assert!(vcpu.mmio_bus.is_some());
}
#[test]
fn test_vm_memory_init() {
let mut vm = Vm::new(false).expect("Cannot create new vm");
let gm = GuestMemoryMmap::from_ranges(&[(
GuestAddress(DRAM_MEM_START_EFI),
0x20_0000, )])
.unwrap();
vm.memory_init(&gm).expect("memory_init failed");
}
#[test]
fn test_configure_vcpu() {
let mem_info = arch::ArchMemoryInfo::default();
let vcpu_list = Arc::new(VcpuList::new(1));
let mut vcpu = Vcpu::new_aarch64(
0,
GuestAddress(0),
None,
EventFd::new(utils::eventfd::EFD_NONBLOCK).unwrap(),
vcpu_list,
false,
)
.unwrap();
assert!(vcpu.configure_aarch64(&mem_info).is_ok());
let vcpu_list = Arc::new(VcpuList::new(2));
let mut vcpu = Vcpu::new_aarch64(
1,
GuestAddress(0),
None,
EventFd::new(utils::eventfd::EFD_NONBLOCK).unwrap(),
vcpu_list,
false,
)
.unwrap();
assert!(vcpu.configure_aarch64(&mem_info).is_ok());
}
#[test]
fn test_vcpu_tls() {
let (mut vcpu, _) = setup_vcpu(0x1000);
assert!(vcpu.reset_thread_local_data().is_err());
vcpu.init_thread_local_data().unwrap();
assert!(vcpu.reset_thread_local_data().is_ok());
assert!(vcpu.reset_thread_local_data().is_err());
}
#[test]
fn test_invalid_tls() {
let (mut vcpu, _) = setup_vcpu(0x1000);
vcpu.init_thread_local_data().unwrap();
vcpu.init_thread_local_data().unwrap_err();
}
#[cfg(target_arch = "x86_64")]
fn queue_event_expect_response(handle: &VcpuHandle, event: VcpuEvent, response: VcpuResponse) {
handle
.send_event(event)
.expect("failed to send event to vcpu");
assert_eq!(
handle
.response_receiver()
.recv_timeout(Duration::from_millis(100))
.expect("did not receive event response from vcpu"),
response
);
}
#[cfg(target_arch = "x86_64")]
fn queue_event_expect_timeout(handle: &VcpuHandle, event: VcpuEvent) {
handle
.send_event(event)
.expect("failed to send event to vcpu");
assert_eq!(
handle
.response_receiver()
.recv_timeout(Duration::from_millis(100)),
Err(RecvTimeoutError::Timeout)
);
}
}