use crate::vmm::PiMutex;
use crate::vmm::{console, kvm, virtio_console};
use kvm_ioctls::VcpuExit;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(target_arch = "aarch64")]
pub(crate) fn dispatch_mmio_write(
com1: &PiMutex<console::Serial>,
com2: &PiMutex<console::Serial>,
virtio_con: Option<&PiMutex<virtio_console::VirtioConsole>>,
addr: u64,
data: &[u8],
) -> bool {
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 {
let base = kvm::VIRTIO_CONSOLE_MMIO_BASE;
if addr >= base && addr < base + virtio_console::VIRTIO_MMIO_SIZE {
vc.lock().mmio_write(addr - base, data);
}
}
false
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn dispatch_mmio_read(
com1: &PiMutex<console::Serial>,
com2: &PiMutex<console::Serial>,
virtio_con: Option<&PiMutex<virtio_console::VirtioConsole>>,
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 {
for b in data.iter_mut() {
*b = 0xff;
}
}
}
#[cfg(target_arch = "aarch64")]
fn mmio_serial_offset(addr: u64, base: u64) -> Option<u8> {
let size = kvm::SERIAL_MMIO_SIZE;
if addr >= base && addr < base + size {
Some((addr - base) as u8)
} else {
None
}
}
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>>>,
kill: &Arc<AtomicBool>,
) {
loop {
if kill.load(Ordering::Acquire) {
break;
}
match vcpu.run() {
Ok(mut exit) => {
if matches!(exit, VcpuExit::Hlt) {
if kill.load(Ordering::Acquire) {
break;
}
continue;
}
match classify_exit(com1, com2, virtio_con.map(|a| a.as_ref()), &mut exit) {
Some(ExitAction::Continue) | None => {}
Some(ExitAction::Shutdown) => {
kill.store(true, Ordering::Release);
break;
}
Some(ExitAction::Fatal(_)) => 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;
}
}
}
const KVM_SYSTEM_EVENT_SHUTDOWN: u32 = 1;
const KVM_SYSTEM_EVENT_RESET: u32 = 2;
pub(crate) enum ExitAction {
Continue,
Shutdown,
Fatal(Option<u64>),
}
pub(crate) fn classify_exit(
com1: &PiMutex<console::Serial>,
com2: &PiMutex<console::Serial>,
virtio_con: Option<&PiMutex<virtio_console::VirtioConsole>>,
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) => {
if dispatch_mmio_write(com1, com2, virtio_con, *addr, data) {
Some(ExitAction::Shutdown)
} else {
Some(ExitAction::Continue)
}
}
#[cfg(target_arch = "aarch64")]
VcpuExit::MmioRead(addr, data) => {
dispatch_mmio_read(com1, com2, virtio_con, *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);
}
}
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);
}
}
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 {
use super::*;
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_out_i8042_reset_is_shutdown_signal() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
assert!(
dispatch_io_out(&com1, &com2, I8042_CMD_PORT, &[I8042_CMD_RESET_CPU]),
"I8042 reset (0xFE to port 0x64) must signal shutdown"
);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_out_i8042_non_reset() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
assert!(!dispatch_io_out(&com1, &com2, I8042_CMD_PORT, &[0x00]));
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_out_serial_com1() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
assert!(!dispatch_io_out(&com1, &com2, console::COM1_BASE, b"A"));
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_out_serial_com2() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
assert!(!dispatch_io_out(&com1, &com2, console::COM2_BASE, b"B"));
let output = com2.lock().output();
assert!(output.contains('B'));
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_out_unknown_port() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
assert!(!dispatch_io_out(&com1, &com2, 0x1234, &[0xFF]));
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_in_i8042_status() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
let mut data = [0xFFu8; 1];
dispatch_io_in(&com1, &com2, I8042_CMD_PORT, &mut data);
assert_eq!(data[0], 0);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_in_i8042_data() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
let mut data = [0xFFu8; 1];
dispatch_io_in(&com1, &com2, I8042_DATA_PORT, &mut data);
assert_eq!(data[0], 0);
}
#[test]
#[cfg(target_arch = "x86_64")]
fn dispatch_io_in_unknown_port() {
let com1 = PiMutex::new(console::Serial::new(console::COM1_BASE));
let com2 = PiMutex::new(console::Serial::new(console::COM2_BASE));
let mut data = [0xFFu8; 1];
dispatch_io_in(&com1, &com2, 0x1234, &mut data);
assert_eq!(data[0], 0xFF, "unknown port should not modify data");
}
}