use super::frame::ContiguousPhysFrames;
use crate::{
X86HostOps, X86HostPhysAddr, X86VcpuResult, host::PhysFrame, svm::vmcb::VmcbStruct,
types::X86_PAGE_SIZE_4K as PAGE_SIZE,
};
#[derive(Debug)]
pub struct VmcbFrame<H: X86HostOps> {
page: PhysFrame<H>,
}
impl<H: X86HostOps> VmcbFrame<H> {
pub const unsafe fn uninit() -> Self {
Self {
page: unsafe { PhysFrame::<H>::uninit() },
}
}
pub fn new() -> X86VcpuResult<Self> {
Ok(Self {
page: PhysFrame::<H>::alloc_zero()?,
})
}
pub fn phys_addr(&self) -> X86HostPhysAddr {
self.page.start_paddr()
}
pub fn as_mut_ptr(&self) -> *mut u8 {
self.page.as_mut_ptr()
}
pub fn as_ptr_vmcb(&self) -> *const VmcbStruct {
self.page.as_mut_ptr() as *const VmcbStruct
}
pub fn as_mut_ptr_vmcb(&self) -> *mut VmcbStruct {
self.page.as_mut_ptr() as *mut VmcbStruct
}
}
#[derive(Debug)]
pub struct IOPm<H: X86HostOps> {
frames: ContiguousPhysFrames<H>,
}
impl<H: X86HostOps> IOPm<H> {
pub fn passthrough_all() -> X86VcpuResult<Self> {
let frames = ContiguousPhysFrames::<H>::alloc_zero(3)?;
let third_frame_start = frames.as_mut_ptr() as usize + 2 * PAGE_SIZE;
unsafe {
*(third_frame_start as *mut u8) |= 0x07;
}
Ok(Self { frames })
}
#[allow(unused)]
pub fn intercept_all() -> X86VcpuResult<Self> {
let mut frames = ContiguousPhysFrames::<H>::alloc(3)?;
frames.fill(0xff);
Ok(Self { frames })
}
pub fn phys_addr(&self) -> X86HostPhysAddr {
self.frames.start_paddr()
}
pub fn set_intercept(&mut self, port: u32, intercept: bool) {
let byte_index = port as usize / 8;
let bit_offset = (port % 8) as u8;
unsafe {
let byte_ptr = self.frames.as_mut_ptr().add(byte_index);
if intercept {
*byte_ptr |= 1 << bit_offset;
} else {
*byte_ptr &= !(1 << bit_offset);
}
}
}
pub fn set_intercept_of_range(&mut self, port_base: u32, count: u32, intercept: bool) {
for port in port_base..port_base + count {
self.set_intercept(port, intercept)
}
}
}
#[derive(Debug)]
pub struct MSRPm<H: X86HostOps> {
frames: ContiguousPhysFrames<H>,
}
impl<H: X86HostOps> MSRPm<H> {
pub fn passthrough_all() -> X86VcpuResult<Self> {
Ok(Self {
frames: ContiguousPhysFrames::<H>::alloc_zero(2)?,
})
}
#[allow(unused)]
pub fn intercept_all() -> X86VcpuResult<Self> {
let mut frames = ContiguousPhysFrames::<H>::alloc(2)?;
frames.fill(0xff);
Ok(Self { frames })
}
pub fn phys_addr(&self) -> X86HostPhysAddr {
self.frames.start_paddr()
}
pub fn set_intercept(&mut self, msr: u32, is_write: bool, intercept: bool) {
let (segment, msr_low) = if msr <= 0x1fff {
(0u32, msr)
} else if (0xc000_0000..=0xc000_1fff).contains(&msr) {
(1u32, msr & 0x1fff)
} else if (0xc001_0000..=0xc001_1fff).contains(&msr) {
(2u32, msr & 0x1fff)
} else {
unreachable!("MSR {:#x} is not covered by the SVM MSRPM", msr);
};
let base_offset = (segment * 2048) as usize;
let byte_in_segment = msr_low as usize / 4;
let bit_pair_offset = ((msr_low & 0b11) * 2) as u8;
let bit_offset = bit_pair_offset + is_write as u8;
unsafe {
let byte_ptr = self.frames.as_mut_ptr().add(base_offset + byte_in_segment);
let old = core::ptr::read_volatile(byte_ptr);
let new = if intercept {
old | (1u8 << bit_offset)
} else {
old & !(1u8 << bit_offset)
};
core::ptr::write_volatile(byte_ptr, new);
}
}
pub fn set_read_intercept(&mut self, msr: u32, intercept: bool) {
self.set_intercept(msr, false, intercept);
}
pub fn set_write_intercept(&mut self, msr: u32, intercept: bool) {
self.set_intercept(msr, true, intercept);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::mock::MockMmHal;
type TestIOPm = IOPm<MockMmHal>;
type TestMSRPm = MSRPm<MockMmHal>;
#[test]
fn svm_permission_maps_use_contiguous_frames() {
MockMmHal::run_test(|| {
{
let iopm = TestIOPm::passthrough_all().unwrap();
assert_eq!(iopm.phys_addr().as_usize(), 0x1000);
assert_eq!(MockMmHal::allocated_count(), 3);
let msrpm = TestMSRPm::passthrough_all().unwrap();
assert_eq!(msrpm.phys_addr().as_usize(), 0x4000);
assert_eq!(MockMmHal::allocated_count(), 5);
}
assert_eq!(MockMmHal::allocated_count(), 0);
});
}
}