use core::{arch::asm, marker::PhantomData};
use super::VirtualizationError;
macro_rules! read_csr {
($csr:literal) => {{
let value;
unsafe { asm!(concat!("csrr {}, ", $csr), out(reg) value, options(nostack)); }
value
}};
}
macro_rules! write_csr {
($csr:literal, $value:expr) => {{
unsafe { asm!(concat!("csrw ", $csr, ", {}"), in(reg) $value, options(nostack)); }
}};
}
#[derive(Debug)]
struct HostState {
hedeleg: usize,
hideleg: usize,
hcounteren: usize,
hvip: usize,
hgatp: usize,
}
#[derive(Debug, Default)]
pub struct PerCpu {
host: Option<HostState>,
max_levels: usize,
_not_send_sync: PhantomData<*mut ()>,
}
impl PerCpu {
pub const fn new() -> Self {
Self {
host: None,
max_levels: 0,
_not_send_sync: PhantomData,
}
}
pub const fn is_enabled(&self) -> bool {
self.host.is_some()
}
pub unsafe fn enable(&mut self) -> Result<(), VirtualizationError> {
if self.host.is_some() {
return Err(VirtualizationError::AlreadyEnabled);
}
if !crate::capability::has_hypervisor_extension() {
return Err(VirtualizationError::Unavailable);
}
let host = HostState {
hedeleg: read_csr!("hedeleg"),
hideleg: read_csr!("hideleg"),
hcounteren: read_csr!("hcounteren"),
hvip: read_csr!("hvip"),
hgatp: read_csr!("hgatp"),
};
let mut max_levels = 0;
for (mode, levels) in [(9usize, 4), (8, 3)] {
write_csr!("hgatp", mode << 60);
let observed: usize = read_csr!("hgatp");
if observed >> 60 == mode {
max_levels = levels;
break;
}
}
write_csr!("hgatp", host.hgatp);
unsafe {
fence_guest_translations();
}
if max_levels == 0 {
return Err(VirtualizationError::UnsupportedPaging);
}
write_csr!(
"hedeleg",
(1usize << 0) | (1 << 2) | (1 << 3) | (1 << 8) | (1 << 12) | (1 << 13) | (1 << 15)
);
write_csr!("hideleg", (1usize << 2) | (1 << 6) | (1 << 10));
write_csr!("hvip", 0usize);
write_csr!("hcounteren", usize::MAX);
self.max_levels = max_levels;
self.host = Some(host);
Ok(())
}
pub unsafe fn disable(&mut self) -> Result<(), VirtualizationError> {
let host = self.host.take().ok_or(VirtualizationError::NotEnabled)?;
write_csr!("hgatp", host.hgatp);
unsafe {
fence_guest_translations();
}
write_csr!("hvip", host.hvip);
write_csr!("hcounteren", host.hcounteren);
write_csr!("hideleg", host.hideleg);
write_csr!("hedeleg", host.hedeleg);
self.max_levels = 0;
Ok(())
}
pub const fn max_guest_page_table_levels(&self) -> usize {
self.max_levels
}
pub const fn guest_phys_addr_bits(&self) -> usize {
match self.max_levels {
3 => 41,
4 => 50,
_ => 0,
}
}
}
unsafe fn fence_guest_translations() {
unsafe {
asm!(
".option push",
".option arch, +h",
"hfence.gvma",
".option pop",
options(nostack)
);
}
}