use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use arcbox_hv::{ExceptionClass, HvVcpu, VcpuExit};
use super::hvc_blk::{
ARCBOX_HVC_BLK_FLUSH, ARCBOX_HVC_BLK_READ, ARCBOX_HVC_BLK_WRITE, ARCBOX_HVC_PROBE,
handle_hvc_blk_flush, handle_hvc_blk_io,
};
use super::psci::{CpuOnSenders, handle_psci};
use super::{HvVcpuIds, Pl011, VcpuThreadHandles};
pub(super) mod reg {
pub use arcbox_hv::reg::{
HV_REG_CPSR as CPSR, HV_REG_PC as PC, HV_REG_X0 as X0, HV_REG_X1 as X1, HV_REG_X2 as X2,
HV_REG_X3 as X3,
};
}
const CPSR_EL1H: u64 = 0x3C5;
const SCTLR_EL1_RESET: u64 = (1 << 11) | (1 << 20) | (1 << 22) | (1 << 23) | (1 << 28) | (1 << 29);
pub(super) struct VcpuContext {
pub device_manager: Arc<crate::device::DeviceManager>,
pub running: Arc<AtomicBool>,
pub paused: Arc<AtomicBool>,
pub pl011: Arc<std::sync::Mutex<Pl011>>,
pub cpu_on_senders: Option<CpuOnSenders>,
pub vcpu_thread_handles: VcpuThreadHandles,
pub hv_vcpu_ids: HvVcpuIds,
pub hvc_blk_fds: Arc<Vec<(i32, u32)>>,
}
fn read_mmio_write_reg(vcpu: &HvVcpu, vcpu_id: u32, register: u8) -> Option<u64> {
if register == 31 {
return Some(0);
}
match vcpu.get_reg(u32::from(register)) {
Ok(v) => Some(v),
Err(e) => {
tracing::error!("vCPU {vcpu_id}: get_reg(X{register}) failed: {e}");
None
}
}
}
pub(super) fn vcpu_run_loop(vcpu_id: u32, entry_addr: u64, x0_value: u64, ctx: VcpuContext) {
let VcpuContext {
device_manager,
running,
paused,
pl011,
cpu_on_senders,
vcpu_thread_handles,
hv_vcpu_ids,
hvc_blk_fds,
} = ctx;
let vcpu = match HvVcpu::new() {
Ok(v) => v,
Err(e) => {
tracing::error!("vCPU {vcpu_id}: creation failed: {e}");
return;
}
};
if let Err(e) = vcpu.set_reg(reg::PC, entry_addr) {
tracing::error!("vCPU {vcpu_id}: set PC failed: {e}");
return;
}
if let Err(e) = vcpu.set_reg(reg::X0, x0_value) {
tracing::error!("vCPU {vcpu_id}: set X0 failed: {e}");
return;
}
let _ = vcpu.set_reg(reg::X1, 0);
let _ = vcpu.set_reg(reg::X2, 0);
let _ = vcpu.set_reg(reg::X3, 0);
if let Err(e) = vcpu.set_reg(reg::CPSR, CPSR_EL1H) {
tracing::error!("vCPU {vcpu_id}: set CPSR failed: {e}");
return;
}
if let Err(e) = vcpu.set_sys_reg(arcbox_hv::sys_reg::HV_SYS_REG_SCTLR_EL1, SCTLR_EL1_RESET) {
tracing::warn!("vCPU {vcpu_id}: set SCTLR_EL1 failed: {e}");
}
{
let mut ids = hv_vcpu_ids
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
ids.push(vcpu.raw_handle());
}
{
let mut handles = vcpu_thread_handles
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
handles.push(std::thread::current());
}
let mpidr = u64::from(vcpu_id) & 0xFF;
if let Err(e) = vcpu.set_sys_reg(arcbox_hv::sys_reg::HV_SYS_REG_MPIDR_EL1, mpidr) {
tracing::warn!("vCPU {vcpu_id}: set MPIDR failed (may not be writable): {e}");
}
tracing::info!(
"vCPU {vcpu_id}: starting at PC={:#x}, X0={:#x}, SCTLR={:#x}",
entry_addr,
x0_value,
SCTLR_EL1_RESET,
);
loop {
if !running.load(Ordering::Relaxed) {
tracing::info!("vCPU {vcpu_id}: shutdown requested");
break;
}
while paused.load(Ordering::Acquire) && running.load(Ordering::Relaxed) {
std::thread::park();
}
if !running.load(Ordering::Relaxed) {
tracing::info!("vCPU {vcpu_id}: shutdown observed after pause");
break;
}
if vcpu_id == 0 && device_manager.poll_bridge_rx() {
if let Some(bid) = device_manager.bridge_device_id() {
device_manager.raise_interrupt_for_device(bid, 1);
}
}
if !running.load(Ordering::Relaxed) {
tracing::trace!("vCPU {vcpu_id}: iteration during shutdown (before vcpu.run)");
}
let exit = match vcpu.run() {
Ok(e) => e,
Err(e) => {
tracing::error!("vCPU {vcpu_id}: run failed: {e}");
running.store(false, Ordering::SeqCst);
break;
}
};
if !running.load(Ordering::Relaxed) {
tracing::trace!(
"vCPU {vcpu_id}: vcpu.run returned during shutdown, exit={:?}",
core::mem::discriminant(&exit)
);
}
match exit {
VcpuExit::Exception {
class: ExceptionClass::DataAbort(ref mmio),
..
} => {
let handled_by_pl011 = {
let uart_match = {
let guard = pl011.lock().unwrap();
guard.contains(mmio.address)
};
if uart_match {
if mmio.is_write {
let value =
read_mmio_write_reg(&vcpu, vcpu_id, mmio.register).unwrap_or(0);
pl011.lock().unwrap().write(
mmio.address,
mmio.access_size as usize,
value,
);
} else {
let value = pl011
.lock()
.unwrap()
.read(mmio.address, mmio.access_size as usize);
if let Err(e) = vcpu.set_reg(u32::from(mmio.register), value) {
tracing::error!(
"vCPU {vcpu_id}: set_reg(X{}) failed: {e}",
mmio.register
);
}
}
true
} else {
false
}
};
if !handled_by_pl011 {
if mmio.is_write {
let Some(value) = read_mmio_write_reg(&vcpu, vcpu_id, mmio.register) else {
let pc = vcpu.get_reg(reg::PC).unwrap_or(0);
let _ = vcpu.set_reg(reg::PC, pc + 4);
continue;
};
tracing::trace!(
"MMIO write: addr={:#x} offset={:#x} X{}={:#x} size={}",
mmio.address,
mmio.address.saturating_sub(
mmio.address & !0xFFF ),
mmio.register,
value,
mmio.access_size,
);
if let Err(e) = device_manager.handle_mmio_write(
mmio.address,
mmio.access_size as usize,
value,
) {
tracing::warn!(
"vCPU {vcpu_id}: MMIO write {:#x} failed: {e}",
mmio.address
);
}
} else {
let value = match device_manager
.handle_mmio_read(mmio.address, mmio.access_size as usize)
{
Ok(v) => v,
Err(e) => {
tracing::warn!(
"vCPU {vcpu_id}: MMIO read {:#x} failed: {e}",
mmio.address
);
0 }
};
if let Err(e) = vcpu.set_reg(u32::from(mmio.register), value) {
tracing::error!(
"vCPU {vcpu_id}: set_reg(X{}) failed: {e}",
mmio.register
);
}
}
}
let pc = vcpu.get_reg(reg::PC).unwrap_or(0);
let _ = vcpu.set_reg(reg::PC, pc + 4);
}
VcpuExit::Exception {
class: ExceptionClass::WaitForInterrupt,
..
} => {
let wfi_has_bridge = device_manager.poll_bridge_rx();
if wfi_has_bridge {
if let Some(bid) = device_manager.bridge_device_id() {
device_manager.raise_interrupt_for_device(bid, 1);
}
continue; }
std::thread::park_timeout(std::time::Duration::from_millis(1));
if !running.load(Ordering::Relaxed) {
break;
}
}
VcpuExit::Exception {
class: ExceptionClass::HypercallHvc(_imm),
..
} => {
let func_id = match vcpu.get_reg(reg::X0) {
Ok(v) => v,
Err(_) => continue,
};
match func_id {
ARCBOX_HVC_PROBE => {
let _ = vcpu.set_reg(reg::X0, hvc_blk_fds.len() as u64);
}
ARCBOX_HVC_BLK_READ => {
let result = handle_hvc_blk_io(&vcpu, &hvc_blk_fds, &device_manager, false);
let _ = vcpu.set_reg(reg::X0, result);
}
ARCBOX_HVC_BLK_WRITE => {
let result = handle_hvc_blk_io(&vcpu, &hvc_blk_fds, &device_manager, true);
let _ = vcpu.set_reg(reg::X0, result);
}
ARCBOX_HVC_BLK_FLUSH => {
let result = handle_hvc_blk_flush(&vcpu, &hvc_blk_fds);
let _ = vcpu.set_reg(reg::X0, result);
}
_ => {
handle_psci(vcpu_id, func_id, &vcpu, &running, cpu_on_senders.as_ref());
if !running.load(Ordering::Relaxed) {
break;
}
}
}
}
VcpuExit::Exception {
class: ExceptionClass::SmcCall(_),
..
} => {
let func_id = match vcpu.get_reg(reg::X0) {
Ok(v) => v,
Err(_) => continue,
};
handle_psci(vcpu_id, func_id, &vcpu, &running, cpu_on_senders.as_ref());
if !running.load(Ordering::Relaxed) {
break;
}
}
VcpuExit::VtimerActivated => {
let _ = vcpu.set_vtimer_mask(false);
}
VcpuExit::Canceled => {
if running.load(Ordering::Relaxed) {
continue;
}
tracing::info!("vCPU {vcpu_id}: canceled (shutdown)");
break;
}
VcpuExit::Exception {
class:
ExceptionClass::SystemRegister {
op0,
op1,
crn,
crm,
op2,
is_write,
rt,
},
..
} => {
if !is_write && rt != 31 {
let _ = vcpu.set_reg(u32::from(rt), 0);
}
if let Ok(pc) = vcpu.get_reg(reg::PC) {
let _ = vcpu.set_reg(reg::PC, pc.wrapping_add(4));
}
tracing::trace!(
vcpu_id,
is_write,
encoding = %format_args!("S{op0}_{op1}_C{crn}_C{crm}_{op2}"),
rt,
"sysreg access treated as RAZ/WI; PC advanced"
);
}
VcpuExit::Exception {
class: ref other, ..
} => {
tracing::warn!("vCPU {vcpu_id}: unhandled exception: {other:?}");
}
VcpuExit::Unknown(reason) => {
tracing::warn!("vCPU {vcpu_id}: unknown exit reason {reason}");
}
}
}
pl011.lock().unwrap().flush();
tracing::info!("vCPU {vcpu_id}: exited");
}