use crate::sync::MutexExt;
use crate::vmm::IoapicHandle;
use crate::vmm::PiMutex;
use crate::vmm::vcpu::{SCX_EXIT_ERROR_THRESHOLD, WatchpointArm, self_arm_watchpoint};
use crate::vmm::{console, kvm, virtio_blk, virtio_console, virtio_net};
use kvm_ioctls::VcpuExit;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use vmm_sys_util::eventfd::EventFd;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[non_exhaustive]
pub struct VcpuRegSnapshot {
pub instruction_pointer: u64,
pub stack_pointer: u64,
pub page_table_root: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub user_page_table_root: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tcr_el1: Option<u64>,
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn capture_vcpu_regs(vcpu: &mut kvm_ioctls::VcpuFd) -> Option<VcpuRegSnapshot> {
let regs = vcpu.get_regs().ok()?;
let sregs = vcpu.get_sregs().ok()?;
Some(VcpuRegSnapshot {
instruction_pointer: regs.rip,
stack_pointer: regs.rsp,
page_table_root: sregs.cr3,
user_page_table_root: None,
tcr_el1: None,
})
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn capture_vcpu_regs(vcpu: &mut kvm_ioctls::VcpuFd) -> Option<VcpuRegSnapshot> {
const KVM_REG_ARM64: u64 = 0x6000_0000_0000_0000;
const KVM_REG_SIZE_U64: u64 = 0x0030_0000_0000_0000;
const KVM_REG_ARM_CORE: u64 = 0x0010_0000;
const SP_EL1_ID: u64 = KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM_CORE | (272 / 4);
const PC_ID: u64 = KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM_CORE | (256 / 4);
const KVM_REG_ARM64_SYSREG: u64 = 0x0013_0000;
const TTBR0_EL1_ID: u64 = KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM64_SYSREG | 0xC100;
const TTBR1_EL1_ID: u64 = KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM64_SYSREG | 0xC101;
const TCR_EL1_ID: u64 = KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM64_SYSREG | 0xC102;
let mut buf = [0u8; 8];
let pc = vcpu
.get_one_reg(PC_ID, &mut buf)
.ok()
.map(|_| u64::from_le_bytes(buf))?;
let sp = vcpu
.get_one_reg(SP_EL1_ID, &mut buf)
.ok()
.map(|_| u64::from_le_bytes(buf))?;
let ttbr1 = vcpu
.get_one_reg(TTBR1_EL1_ID, &mut buf)
.ok()
.map(|_| u64::from_le_bytes(buf))
.unwrap_or(0);
let ttbr0 = vcpu
.get_one_reg(TTBR0_EL1_ID, &mut buf)
.ok()
.map(|_| u64::from_le_bytes(buf));
let tcr_el1 = vcpu
.get_one_reg(TCR_EL1_ID, &mut buf)
.ok()
.map(|_| u64::from_le_bytes(buf));
Some(VcpuRegSnapshot {
instruction_pointer: pc,
stack_pointer: sp,
page_table_root: ttbr1,
user_page_table_root: ttbr0,
tcr_el1,
})
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn read_tcr_el1(_vcpu: &mut kvm_ioctls::VcpuFd) -> Option<u64> {
None
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn read_tcr_el1(vcpu: &mut kvm_ioctls::VcpuFd) -> Option<u64> {
const KVM_REG_ARM64: u64 = 0x6000_0000_0000_0000;
const KVM_REG_SIZE_U64: u64 = 0x0030_0000_0000_0000;
const KVM_REG_ARM64_SYSREG: u64 = 0x0013_0000;
const TCR_EL1_ID: u64 = KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM64_SYSREG | 0xC102;
let mut buf = [0u8; 8];
vcpu.get_one_reg(TCR_EL1_ID, &mut buf)
.ok()
.map(|_| u64::from_le_bytes(buf))
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn read_cr3(vcpu: &mut kvm_ioctls::VcpuFd) -> Option<u64> {
vcpu.get_sregs().ok().map(|s| s.cr3)
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn read_cr3(vcpu: &mut kvm_ioctls::VcpuFd) -> Option<u64> {
const KVM_REG_ARM64: u64 = 0x6000_0000_0000_0000;
const KVM_REG_SIZE_U64: u64 = 0x0030_0000_0000_0000;
const KVM_REG_ARM64_SYSREG: u64 = 0x0013_0000;
const TTBR1_EL1_ID: u64 = KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM64_SYSREG | 0xC101;
let mut buf = [0u8; 8];
vcpu.get_one_reg(TTBR1_EL1_ID, &mut buf)
.ok()
.map(|_| u64::from_le_bytes(buf))
}
impl std::fmt::Display for VcpuRegSnapshot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"ip=0x{:016x} sp=0x{:016x} ptroot=0x{:016x}",
self.instruction_pointer, self.stack_pointer, self.page_table_root
)?;
if let Some(uptr) = self.user_page_table_root {
write!(f, " uptroot=0x{uptr:016x}")?;
}
Ok(())
}
}
#[cfg(target_arch = "aarch64")]
#[allow(clippy::too_many_arguments)]
pub(crate) fn dispatch_mmio_write(
com1: &PiMutex<console::Serial>,
com2: &PiMutex<console::Serial>,
virtio_con: Option<&PiMutex<virtio_console::VirtioConsole>>,
virtio_blk: Option<&PiMutex<virtio_blk::VirtioBlk>>,
virtio_net: Option<&PiMutex<virtio_net::VirtioNet>>,
addr: u64,
data: &[u8],
) {
if let Some(offset) = mmio_serial_offset(addr, kvm::SERIAL_MMIO_BASE) {
if let Some(&byte) = data.first() {
com1.lock().inner_write(offset, byte);
}
} else if let Some(offset) = mmio_serial_offset(addr, kvm::SERIAL2_MMIO_BASE)
&& let Some(&byte) = data.first()
{
com2.lock().inner_write(offset, byte);
} else if let Some(vc) = virtio_con
&& (kvm::VIRTIO_CONSOLE_MMIO_BASE
..kvm::VIRTIO_CONSOLE_MMIO_BASE + virtio_console::VIRTIO_MMIO_SIZE)
.contains(&addr)
{
vc.lock()
.mmio_write(addr - kvm::VIRTIO_CONSOLE_MMIO_BASE, data);
} else if let Some(vb) = virtio_blk
&& (kvm::VIRTIO_BLK_MMIO_BASE..kvm::VIRTIO_BLK_MMIO_BASE + virtio_blk::VIRTIO_MMIO_SIZE)
.contains(&addr)
{
vb.lock().mmio_write(addr - kvm::VIRTIO_BLK_MMIO_BASE, data);
} else if let Some(vn) = virtio_net
&& (kvm::VIRTIO_NET_MMIO_BASE..kvm::VIRTIO_NET_MMIO_BASE + virtio_net::VIRTIO_MMIO_SIZE)
.contains(&addr)
{
vn.lock().mmio_write(addr - kvm::VIRTIO_NET_MMIO_BASE, data);
}
}
#[cfg(target_arch = "aarch64")]
#[allow(clippy::too_many_arguments)]
pub(crate) fn dispatch_mmio_read(
com1: &PiMutex<console::Serial>,
com2: &PiMutex<console::Serial>,
virtio_con: Option<&PiMutex<virtio_console::VirtioConsole>>,
virtio_blk: Option<&PiMutex<virtio_blk::VirtioBlk>>,
virtio_net: Option<&PiMutex<virtio_net::VirtioNet>>,
addr: u64,
data: &mut [u8],
) {
if let Some(offset) = mmio_serial_offset(addr, kvm::SERIAL_MMIO_BASE) {
if let Some(first) = data.first_mut() {
*first = com1.lock().inner_read(offset);
}
} else if let Some(offset) = mmio_serial_offset(addr, kvm::SERIAL2_MMIO_BASE) {
if let Some(first) = data.first_mut() {
*first = com2.lock().inner_read(offset);
}
} else if let Some(vc) = virtio_con
&& (kvm::VIRTIO_CONSOLE_MMIO_BASE
..kvm::VIRTIO_CONSOLE_MMIO_BASE + virtio_console::VIRTIO_MMIO_SIZE)
.contains(&addr)
{
vc.lock()
.mmio_read(addr - kvm::VIRTIO_CONSOLE_MMIO_BASE, data);
} else if let Some(vb) = virtio_blk
&& (kvm::VIRTIO_BLK_MMIO_BASE..kvm::VIRTIO_BLK_MMIO_BASE + virtio_blk::VIRTIO_MMIO_SIZE)
.contains(&addr)
{
vb.lock().mmio_read(addr - kvm::VIRTIO_BLK_MMIO_BASE, data);
} else if let Some(vn) = virtio_net
&& (kvm::VIRTIO_NET_MMIO_BASE..kvm::VIRTIO_NET_MMIO_BASE + virtio_net::VIRTIO_MMIO_SIZE)
.contains(&addr)
{
vn.lock().mmio_read(addr - kvm::VIRTIO_NET_MMIO_BASE, data);
} else {
for b in data.iter_mut() {
*b = 0xff;
}
}
}
#[cfg(target_arch = "aarch64")]
fn mmio_serial_offset(addr: u64, base: u64) -> Option<u8> {
const MAX_REG_OFFSET: u64 = u8::MAX as u64 + 1;
const _: () = assert!(
kvm::SERIAL_MMIO_SIZE >= MAX_REG_OFFSET,
"SERIAL_MMIO_SIZE must cover at least the 256-byte u8-representable \
register window mmio_serial_offset accepts"
);
if addr >= base && addr < base + MAX_REG_OFFSET {
Some((addr - base) as u8)
} else {
None
}
}
#[cfg(target_arch = "aarch64")]
const ESR_ELX_EC_WATCHPT_LOW: u32 = 0x34;
#[cfg(target_arch = "aarch64")]
const ESR_ELX_EC_SOFTSTP_LOW: u32 = 0x32;
#[cfg(target_arch = "aarch64")]
const ESR_ELX_EC_SHIFT: u32 = 26;
#[cfg(target_arch = "aarch64")]
const ESR_ELX_EC_MASK: u32 = 0x3F;
pub(crate) fn dispatch_watchpoint_hit(
watchpoint: &WatchpointArm,
debug_arch: &kvm_bindings::kvm_debug_exit_arch,
armed_slots: &[u64; 4],
single_step_pending: &mut bool,
single_step_slot: &mut usize,
) {
#[cfg(target_arch = "x86_64")]
{
let _ = armed_slots;
let _ = (&mut *single_step_pending, &mut *single_step_slot);
let dr6 = debug_arch.dr6;
let trap_bits = (dr6 & 0xF) as u8;
if trap_bits == 0 {
tracing::debug!(
dr6,
"KVM_EXIT_DEBUG fired with no DR0..DR3 trap bit set \
(BS/BT or spurious); not latching"
);
return;
}
if trap_bits & 0x1 != 0 {
latch_slot0_with_gate(watchpoint);
}
for idx in 0..3 {
if trap_bits & (1u8 << (idx + 1)) != 0 {
watchpoint.latch_user_hit(idx);
}
}
}
#[cfg(target_arch = "aarch64")]
{
let ec = (debug_arch.hsr >> ESR_ELX_EC_SHIFT) & ESR_ELX_EC_MASK;
if ec == ESR_ELX_EC_SOFTSTP_LOW {
if *single_step_pending {
*single_step_pending = false;
*single_step_slot = 0;
} else {
tracing::debug!(
hsr = debug_arch.hsr,
"KVM_EXIT_DEBUG soft-step EC with no \
single-step pending; ignoring (likely \
spurious kernel-side step exit)"
);
}
return;
}
if ec != ESR_ELX_EC_WATCHPT_LOW {
tracing::debug!(
hsr = debug_arch.hsr,
ec,
"KVM_EXIT_DEBUG with non-watchpoint EC; ignoring \
(breakpoint/BRK paths are not used by ktstr)"
);
return;
}
let far = debug_arch.far;
let mut matched_mask: u8 = 0;
for (i, kva) in armed_slots.iter().enumerate() {
if *kva == 0 {
continue;
}
if far >= *kva && far < kva.saturating_add(4) {
matched_mask |= 1 << i;
if i == 0 {
latch_slot0_with_gate(watchpoint);
} else {
watchpoint.latch_user_hit(i - 1);
}
}
}
if matched_mask == 0 {
tracing::debug!(
hsr = debug_arch.hsr,
far,
armed = ?armed_slots,
"KVM_EXIT_DEBUG watchpoint fired but FAR matched no \
armed slot (possible KVM watchpoint match-distance \
fallback or stale arm); not latching"
);
return;
}
*single_step_pending = true;
*single_step_slot = matched_mask as usize;
}
}
fn latch_slot0_with_gate(watchpoint: &WatchpointArm) {
let host_ptr = watchpoint.kind_host_ptr.load(Ordering::Acquire);
if host_ptr.is_null() {
tracing::error!(
"latch_slot0_with_gate: kind_host_ptr null at fire time — \
publication invariant broken (request_kva non-zero must \
imply kind_host_ptr non-null per the Release-store \
ordering in freeze_coord.rs::run_coord_loop). Skipping \
slot-0 latch; the BPF .bss late-trigger fallback in the \
freeze coordinator's poll loop remains active."
);
return;
}
std::sync::atomic::fence(Ordering::Acquire);
let kind = unsafe { std::ptr::read_volatile(host_ptr) };
if kind >= SCX_EXIT_ERROR_THRESHOLD {
watchpoint.latch_hit();
} else {
tracing::debug!(
kind,
threshold = SCX_EXIT_ERROR_THRESHOLD,
"watchpoint fired on non-error exit_kind transition \
(e.g. SCX_EXIT_DONE on clean shutdown); skipping \
freeze trigger"
);
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn vcpu_run_loop_unified(
vcpu: &mut kvm_ioctls::VcpuFd,
com1: &Arc<PiMutex<console::Serial>>,
com2: &Arc<PiMutex<console::Serial>>,
virtio_con: Option<&Arc<PiMutex<virtio_console::VirtioConsole>>>,
virtio_blk: Option<&Arc<PiMutex<virtio_blk::VirtioBlk>>>,
virtio_net: Option<&Arc<PiMutex<virtio_net::VirtioNet>>>,
ioapic: Option<&Arc<IoapicHandle>>,
kill: &Arc<AtomicBool>,
kill_evt: &Arc<EventFd>,
freeze: &Arc<AtomicBool>,
parked: &Arc<AtomicBool>,
regs_slot: &Arc<std::sync::Mutex<Option<VcpuRegSnapshot>>>,
watchpoint: &Arc<WatchpointArm>,
has_immediate_exit: bool,
parked_evt: Option<&Arc<EventFd>>,
thaw_evt: Option<&Arc<EventFd>>,
) {
let mut armed_slots: [u64; 4] = [0; 4];
let mut arm_failures: u8 = 0;
let mut single_step_pending: bool = false;
let mut single_step_slot: usize = 0;
let mut armed_single_step: bool = false;
loop {
if kill.load(Ordering::Acquire) {
break;
}
if freeze.load(Ordering::Acquire) {
handle_freeze(
vcpu,
has_immediate_exit,
kill,
freeze,
parked,
regs_slot,
parked_evt.map(|a| a.as_ref()),
thaw_evt.map(|a| a.as_ref()),
Some(kill_evt.as_ref()),
);
if kill.load(Ordering::Acquire) {
break;
}
}
self_arm_watchpoint(
vcpu,
watchpoint,
&mut armed_slots,
&mut arm_failures,
single_step_pending,
single_step_slot,
&mut armed_single_step,
);
match vcpu.run() {
Ok(mut exit) => {
if matches!(exit, VcpuExit::Hlt) {
if kill.load(Ordering::Acquire) {
break;
}
continue;
}
if let VcpuExit::Debug(debug_arch) = &exit {
dispatch_watchpoint_hit(
watchpoint,
debug_arch,
&armed_slots,
&mut single_step_pending,
&mut single_step_slot,
);
if kill.load(Ordering::Acquire) {
break;
}
continue;
}
match classify_exit(
com1,
com2,
virtio_con.map(|a| a.as_ref()),
virtio_blk.map(|a| a.as_ref()),
virtio_net.map(|a| a.as_ref()),
ioapic.map(|a| a.as_ref()),
&mut exit,
) {
Some(ExitAction::Continue) | None => {}
Some(ExitAction::Shutdown) => {
kill.store(true, Ordering::Release);
let _ = kill_evt.write(1);
break;
}
Some(ExitAction::Fatal(_)) => {
tracing::error!("AP fatal exit");
kill.store(true, Ordering::Release);
let _ = kill_evt.write(1);
break;
}
}
}
Err(e) => {
if e.errno() == libc::EINTR || e.errno() == libc::EAGAIN {
vcpu.set_kvm_immediate_exit(0);
if kill.load(Ordering::Acquire) {
break;
}
continue;
}
if kill.load(Ordering::Acquire) {
break;
}
}
}
if kill.load(Ordering::Acquire) {
break;
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn handle_freeze(
vcpu: &mut kvm_ioctls::VcpuFd,
has_immediate_exit: bool,
kill: &Arc<AtomicBool>,
freeze: &Arc<AtomicBool>,
parked: &Arc<AtomicBool>,
regs_slot: &Arc<std::sync::Mutex<Option<VcpuRegSnapshot>>>,
parked_evt: Option<&EventFd>,
thaw_evt: Option<&EventFd>,
kill_evt: Option<&EventFd>,
) {
if has_immediate_exit {
vcpu.set_kvm_immediate_exit(1);
if let Err(e) = vcpu.run()
&& e.errno() != libc::EINTR
{
tracing::warn!(
err = %e,
"handle_freeze: drain KVM_RUN failed with non-EINTR — \
pending PIO/MMIO may not have committed before park"
);
}
vcpu.set_kvm_immediate_exit(0);
}
let snapshot = capture_vcpu_regs(vcpu);
*regs_slot.lock_unpoisoned() = snapshot;
parked.store(true, Ordering::Release);
if let Some(evt) = parked_evt
&& let Err(e) = evt.write(1)
{
if e.raw_os_error() == Some(libc::EAGAIN) {
tracing::debug!(
err = %e,
"handle_freeze: parked_evt write returned EAGAIN \
(eventfd counter saturated; benign — coordinator \
already has a pending wake edge)"
);
} else {
tracing::warn!(
err = %e,
"handle_freeze: parked_evt write failed with non-EAGAIN \
errno — eventfd may be broken; freeze coordinator wake \
falls back to epoll backstop"
);
}
}
use std::os::fd::AsRawFd;
while freeze.load(Ordering::Acquire) {
if kill.load(Ordering::Acquire) {
break;
}
match (thaw_evt, kill_evt) {
(Some(thaw), kev) => {
let mut pfds = [
libc::pollfd {
fd: thaw.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
},
libc::pollfd {
fd: kev.map_or(-1, |k| k.as_raw_fd()),
events: libc::POLLIN,
revents: 0,
},
];
let nfds = if kev.is_some() { 2 } else { 1 };
unsafe {
libc::poll(pfds.as_mut_ptr(), nfds as libc::nfds_t, 100);
}
}
(None, _) => {
std::thread::park_timeout(std::time::Duration::from_millis(10));
}
}
}
parked.store(false, Ordering::Release);
}
const KVM_SYSTEM_EVENT_SHUTDOWN: u32 = 1;
const KVM_SYSTEM_EVENT_RESET: u32 = 2;
pub(crate) enum ExitAction {
Continue,
Shutdown,
Fatal(Option<u64>),
}
#[allow(clippy::too_many_arguments)]
#[cfg_attr(not(target_arch = "x86_64"), allow(unused_variables))]
pub(crate) fn classify_exit(
com1: &PiMutex<console::Serial>,
com2: &PiMutex<console::Serial>,
virtio_con: Option<&PiMutex<virtio_console::VirtioConsole>>,
virtio_blk: Option<&PiMutex<virtio_blk::VirtioBlk>>,
virtio_net: Option<&PiMutex<virtio_net::VirtioNet>>,
ioapic: Option<&IoapicHandle>,
exit: &mut VcpuExit,
) -> Option<ExitAction> {
match exit {
#[cfg(target_arch = "x86_64")]
VcpuExit::IoOut(port, data) => {
if dispatch_io_out(com1, com2, *port, data) {
Some(ExitAction::Shutdown)
} else {
Some(ExitAction::Continue)
}
}
#[cfg(target_arch = "x86_64")]
VcpuExit::IoIn(port, data) => {
dispatch_io_in(com1, com2, *port, data);
Some(ExitAction::Continue)
}
#[cfg(target_arch = "aarch64")]
VcpuExit::MmioWrite(addr, data) => {
dispatch_mmio_write(com1, com2, virtio_con, virtio_blk, virtio_net, *addr, data);
Some(ExitAction::Continue)
}
#[cfg(target_arch = "aarch64")]
VcpuExit::MmioRead(addr, data) => {
dispatch_mmio_read(com1, com2, virtio_con, virtio_blk, virtio_net, *addr, data);
Some(ExitAction::Continue)
}
VcpuExit::Hlt => None,
VcpuExit::Shutdown => Some(ExitAction::Shutdown),
VcpuExit::SystemEvent(event_type, _) => {
if *event_type == KVM_SYSTEM_EVENT_SHUTDOWN || *event_type == KVM_SYSTEM_EVENT_RESET {
Some(ExitAction::Shutdown)
} else {
Some(ExitAction::Continue)
}
}
VcpuExit::FailEntry(reason, _cpu) => Some(ExitAction::Fatal(Some(*reason))),
VcpuExit::InternalError => Some(ExitAction::Fatal(None)),
#[cfg(target_arch = "x86_64")]
VcpuExit::MmioRead(addr, data) => {
if let Some(vc) = virtio_con {
let base = kvm::VIRTIO_CONSOLE_MMIO_BASE;
if *addr >= base && *addr < base + virtio_console::VIRTIO_MMIO_SIZE {
vc.lock().mmio_read(*addr - base, data);
return Some(ExitAction::Continue);
}
}
if let Some(vb) = virtio_blk {
let base = kvm::VIRTIO_BLK_MMIO_BASE;
if *addr >= base && *addr < base + virtio_blk::VIRTIO_MMIO_SIZE {
vb.lock().mmio_read(*addr - base, data);
return Some(ExitAction::Continue);
}
}
if let Some(vn) = virtio_net {
let base = kvm::VIRTIO_NET_MMIO_BASE;
if *addr >= base && *addr < base + virtio_net::VIRTIO_MMIO_SIZE {
vn.lock().mmio_read(*addr - base, data);
return Some(ExitAction::Continue);
}
}
if let Some(io) = ioapic
&& let Some(off) = io.in_range(*addr)
{
io.mmio_read(off, data);
return Some(ExitAction::Continue);
}
for b in data.iter_mut() {
*b = 0xff;
}
Some(ExitAction::Continue)
}
#[cfg(target_arch = "x86_64")]
VcpuExit::MmioWrite(addr, data) => {
if let Some(vc) = virtio_con {
let base = kvm::VIRTIO_CONSOLE_MMIO_BASE;
if *addr >= base && *addr < base + virtio_console::VIRTIO_MMIO_SIZE {
vc.lock().mmio_write(*addr - base, data);
return Some(ExitAction::Continue);
}
}
if let Some(vb) = virtio_blk {
let base = kvm::VIRTIO_BLK_MMIO_BASE;
if *addr >= base && *addr < base + virtio_blk::VIRTIO_MMIO_SIZE {
vb.lock().mmio_write(*addr - base, data);
return Some(ExitAction::Continue);
}
}
if let Some(vn) = virtio_net {
let base = kvm::VIRTIO_NET_MMIO_BASE;
if *addr >= base && *addr < base + virtio_net::VIRTIO_MMIO_SIZE {
vn.lock().mmio_write(*addr - base, data);
return Some(ExitAction::Continue);
}
}
if let Some(io) = ioapic
&& let Some(off) = io.in_range(*addr)
{
if let Err(e) = io.mmio_write(off, data) {
tracing::error!(
count = io.routing_failures(),
"ioapic: KVM_SET_GSI_ROUTING failed: {e:#}"
);
}
return Some(ExitAction::Continue);
}
Some(ExitAction::Continue)
}
#[cfg(target_arch = "x86_64")]
VcpuExit::IoapicEoi(vector) => {
if let Some(io) = ioapic {
io.eoi(*vector);
}
Some(ExitAction::Continue)
}
_ => None,
}
}
#[cfg(target_arch = "x86_64")]
const I8042_DATA_PORT: u16 = 0x60;
#[cfg(target_arch = "x86_64")]
const I8042_CMD_PORT: u16 = 0x64;
#[cfg(target_arch = "x86_64")]
const I8042_CMD_RESET_CPU: u8 = 0xFE;
#[cfg(target_arch = "x86_64")]
fn dispatch_io_out(
com1: &PiMutex<console::Serial>,
com2: &PiMutex<console::Serial>,
port: u16,
data: &[u8],
) -> bool {
if port == I8042_CMD_PORT && data.first() == Some(&I8042_CMD_RESET_CPU) {
return true;
}
if (console::COM1_BASE..console::COM1_BASE + 8).contains(&port) {
com1.lock().handle_out(port, data);
} else if (console::COM2_BASE..console::COM2_BASE + 8).contains(&port) {
com2.lock().handle_out(port, data);
}
false
}
#[cfg(target_arch = "x86_64")]
fn dispatch_io_in(
com1: &PiMutex<console::Serial>,
com2: &PiMutex<console::Serial>,
port: u16,
data: &mut [u8],
) {
match port {
I8042_CMD_PORT => {
if let Some(b) = data.first_mut() {
*b = 0;
}
}
I8042_DATA_PORT => {
if let Some(b) = data.first_mut() {
*b = 0;
}
}
p if (console::COM1_BASE..console::COM1_BASE + 8).contains(&p) => {
com1.lock().handle_in(port, data);
}
p if (console::COM2_BASE..console::COM2_BASE + 8).contains(&p) => {
com2.lock().handle_in(port, data);
}
_ => {}
}
}
#[cfg(all(test, target_arch = "x86_64"))]
mod tests;
#[cfg(test)]
mod tests_arch_neutral;
#[cfg(all(test, target_arch = "aarch64"))]
mod tests_aarch64;
#[cfg(all(test, target_arch = "x86_64"))]
mod handle_freeze_tests;
#[cfg(test)]
mod vcpu_reg_snapshot_tests;