Skip to content

Commit d5808f7

Browse files
committed
Step 2: add struct Vcpu and setup VMCS
1 parent cb87672 commit d5808f7

8 files changed

Lines changed: 857 additions & 6 deletions

File tree

hypervisor/src/hv/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ pub fn run() {
99
println!("Hardware support: {:?}", rvm::has_hardware_support());
1010

1111
let mut percpu = RvmPerCpu::<RvmHalImpl>::new(0);
12-
let res = percpu.hardware_enable();
13-
println!("Hardware enable: {:?}", res);
12+
percpu.hardware_enable().unwrap();
13+
14+
let mut vcpu = percpu.create_vcpu().unwrap();
15+
info!("{:#x?}", vcpu);
16+
vcpu.run();
1417
}

rvm/src/arch/x86_64/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub(crate) mod msr;
2+
pub(crate) mod regs;
23

34
cfg_if::cfg_if! {
45
if #[cfg(feature = "vmx")] {
@@ -7,4 +8,4 @@ cfg_if::cfg_if! {
78
}
89
}
910

10-
pub use vender::{has_hardware_support, ArchPerCpuState};
11+
pub use vender::{has_hardware_support, ArchPerCpuState, RvmVcpu};

rvm/src/arch/x86_64/msr.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,38 @@ use x86::msr::{rdmsr, wrmsr};
33
/// X86 model-specific registers. (SDM Vol. 4)
44
#[repr(u32)]
55
#[derive(Debug, Copy, Clone)]
6-
#[allow(non_camel_case_types)]
6+
#[allow(non_camel_case_types, dead_code)]
77
pub enum Msr {
88
IA32_FEATURE_CONTROL = 0x3a,
9-
IA32_VMX_BASIC = 0x480,
109

10+
IA32_PAT = 0x277,
11+
12+
IA32_VMX_BASIC = 0x480,
13+
IA32_VMX_PINBASED_CTLS = 0x481,
14+
IA32_VMX_PROCBASED_CTLS = 0x482,
15+
IA32_VMX_EXIT_CTLS = 0x483,
16+
IA32_VMX_ENTRY_CTLS = 0x484,
17+
IA32_VMX_MISC = 0x485,
1118
IA32_VMX_CR0_FIXED0 = 0x486,
1219
IA32_VMX_CR0_FIXED1 = 0x487,
1320
IA32_VMX_CR4_FIXED0 = 0x488,
1421
IA32_VMX_CR4_FIXED1 = 0x489,
22+
IA32_VMX_PROCBASED_CTLS2 = 0x48b,
23+
IA32_VMX_EPT_VPID_CAP = 0x48c,
24+
IA32_VMX_TRUE_PINBASED_CTLS = 0x48d,
25+
IA32_VMX_TRUE_PROCBASED_CTLS = 0x48e,
26+
IA32_VMX_TRUE_EXIT_CTLS = 0x48f,
27+
IA32_VMX_TRUE_ENTRY_CTLS = 0x490,
28+
29+
IA32_EFER = 0xc000_0080,
30+
IA32_STAR = 0xc000_0081,
31+
IA32_LSTAR = 0xc000_0082,
32+
IA32_CSTAR = 0xc000_0083,
33+
IA32_FMASK = 0xc000_0084,
34+
35+
IA32_FS_BASE = 0xc000_0100,
36+
IA32_GS_BASE = 0xc000_0101,
37+
IA32_KERNEL_GSBASE = 0xc000_0102,
1538
}
1639

1740
impl Msr {

rvm/src/arch/x86_64/regs.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// General-Purpose Registers for 64-bit x86 architecture.
2+
#[repr(C)]
3+
#[derive(Debug, Default, Clone)]
4+
pub struct GeneralRegisters {
5+
pub rax: u64,
6+
pub rcx: u64,
7+
pub rdx: u64,
8+
pub rbx: u64,
9+
_unused_rsp: u64,
10+
pub rbp: u64,
11+
pub rsi: u64,
12+
pub rdi: u64,
13+
pub r8: u64,
14+
pub r9: u64,
15+
pub r10: u64,
16+
pub r11: u64,
17+
pub r12: u64,
18+
pub r13: u64,
19+
pub r14: u64,
20+
pub r15: u64,
21+
}

rvm/src/arch/x86_64/vmx/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
mod structs;
2+
mod vcpu;
3+
mod vmcs;
24

35
use raw_cpuid::CpuId;
46
use x86::{bits64::vmx, vmx::VmFail};
@@ -9,6 +11,7 @@ use crate::arch::msr::Msr;
911
use crate::error::{RvmError, RvmResult};
1012
use crate::hal::RvmHal;
1113

14+
pub use self::vcpu::VmxVcpu as RvmVcpu;
1215
pub use self::VmxPerCpuState as ArchPerCpuState;
1316

1417
pub fn has_hardware_support() -> bool {

rvm/src/arch/x86_64/vmx/vcpu.rs

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
use core::fmt::{Debug, Formatter, Result};
2+
3+
use bit_field::BitField;
4+
use x86::bits64::vmx;
5+
use x86::dtables::{self, DescriptorTablePointer};
6+
use x86::segmentation::SegmentSelector;
7+
use x86_64::registers::control::{Cr0, Cr0Flags, Cr3, Cr4, Cr4Flags};
8+
9+
use super::structs::VmxRegion;
10+
use super::vmcs::{
11+
self, VmcsControl32, VmcsControl64, VmcsControlNW, VmcsGuest16, VmcsGuest32, VmcsGuest64,
12+
VmcsGuestNW, VmcsHost16, VmcsHost32, VmcsHost64, VmcsHostNW,
13+
};
14+
use super::VmxPerCpuState;
15+
use crate::arch::{msr::Msr, regs::GeneralRegisters};
16+
use crate::{RvmHal, RvmResult};
17+
18+
/// A virtual CPU within a guest.
19+
#[repr(C)]
20+
pub struct VmxVcpu<H: RvmHal> {
21+
guest_regs: GeneralRegisters,
22+
vmcs: VmxRegion<H>,
23+
}
24+
25+
impl<H: RvmHal> VmxVcpu<H> {
26+
pub(crate) fn new(percpu: &VmxPerCpuState<H>) -> RvmResult<Self> {
27+
let mut vcpu = Self {
28+
guest_regs: GeneralRegisters::default(),
29+
vmcs: VmxRegion::new(percpu.vmcs_revision_id, false)?,
30+
};
31+
vcpu.setup_vmcs()?;
32+
info!("[RVM] created VmxVcpu(vmcs: {:#x})", vcpu.vmcs.phys_addr());
33+
Ok(vcpu)
34+
}
35+
36+
pub fn run(&mut self) {}
37+
}
38+
39+
// Implementation of private methods
40+
impl<H: RvmHal> VmxVcpu<H> {
41+
fn setup_vmcs(&mut self) -> RvmResult {
42+
let paddr = self.vmcs.phys_addr() as u64;
43+
unsafe {
44+
vmx::vmclear(paddr)?;
45+
vmx::vmptrld(paddr)?;
46+
}
47+
self.setup_vmcs_host()?;
48+
self.setup_vmcs_guest()?;
49+
self.setup_vmcs_control()?;
50+
Ok(())
51+
}
52+
53+
fn setup_vmcs_host(&mut self) -> RvmResult {
54+
VmcsHost64::IA32_PAT.write(Msr::IA32_PAT.read())?;
55+
VmcsHost64::IA32_EFER.write(Msr::IA32_EFER.read())?;
56+
57+
VmcsHostNW::CR0.write(Cr0::read_raw() as _)?;
58+
VmcsHostNW::CR3.write(Cr3::read_raw().0.start_address().as_u64() as _)?;
59+
VmcsHostNW::CR4.write(Cr4::read_raw() as _)?;
60+
61+
VmcsHost16::ES_SELECTOR.write(x86::segmentation::es().bits())?;
62+
VmcsHost16::CS_SELECTOR.write(x86::segmentation::cs().bits())?;
63+
VmcsHost16::SS_SELECTOR.write(x86::segmentation::ss().bits())?;
64+
VmcsHost16::DS_SELECTOR.write(x86::segmentation::ds().bits())?;
65+
VmcsHost16::FS_SELECTOR.write(x86::segmentation::fs().bits())?;
66+
VmcsHost16::GS_SELECTOR.write(x86::segmentation::gs().bits())?;
67+
VmcsHostNW::FS_BASE.write(Msr::IA32_FS_BASE.read() as _)?;
68+
VmcsHostNW::GS_BASE.write(Msr::IA32_GS_BASE.read() as _)?;
69+
70+
let tr = unsafe { x86::task::tr() };
71+
let mut gdtp = DescriptorTablePointer::<u64>::default();
72+
let mut idtp = DescriptorTablePointer::<u64>::default();
73+
unsafe {
74+
dtables::sgdt(&mut gdtp);
75+
dtables::sidt(&mut idtp);
76+
}
77+
VmcsHost16::TR_SELECTOR.write(tr.bits())?;
78+
VmcsHostNW::TR_BASE.write(get_tr_base(tr, &gdtp) as _)?;
79+
VmcsHostNW::GDTR_BASE.write(gdtp.base as _)?;
80+
VmcsHostNW::IDTR_BASE.write(idtp.base as _)?;
81+
82+
VmcsHostNW::IA32_SYSENTER_ESP.write(0)?;
83+
VmcsHostNW::IA32_SYSENTER_EIP.write(0)?;
84+
VmcsHost32::IA32_SYSENTER_CS.write(0)?;
85+
86+
VmcsHostNW::RSP.write(0)?; // TODO
87+
VmcsHostNW::RIP.write(0)?; // TODO
88+
Ok(())
89+
}
90+
91+
fn setup_vmcs_guest(&mut self) -> RvmResult {
92+
let cr0_guest = Cr0Flags::EXTENSION_TYPE | Cr0Flags::NUMERIC_ERROR;
93+
let cr0_host_owned =
94+
Cr0Flags::NUMERIC_ERROR | Cr0Flags::NOT_WRITE_THROUGH | Cr0Flags::CACHE_DISABLE;
95+
let cr0_read_shadow = Cr0Flags::NUMERIC_ERROR;
96+
VmcsGuestNW::CR0.write(cr0_guest.bits() as _)?;
97+
VmcsControlNW::CR0_GUEST_HOST_MASK.write(cr0_host_owned.bits() as _)?;
98+
VmcsControlNW::CR0_READ_SHADOW.write(cr0_read_shadow.bits() as _)?;
99+
100+
let cr4_guest = Cr4Flags::VIRTUAL_MACHINE_EXTENSIONS;
101+
let cr4_host_owned = Cr4Flags::VIRTUAL_MACHINE_EXTENSIONS;
102+
let cr4_read_shadow = 0;
103+
VmcsGuestNW::CR4.write(cr4_guest.bits() as _)?;
104+
VmcsControlNW::CR4_GUEST_HOST_MASK.write(cr4_host_owned.bits() as _)?;
105+
VmcsControlNW::CR4_READ_SHADOW.write(cr4_read_shadow)?;
106+
107+
macro_rules! set_guest_segment {
108+
($seg: ident, $access_rights: expr) => {{
109+
use VmcsGuest16::*;
110+
use VmcsGuest32::*;
111+
use VmcsGuestNW::*;
112+
concat_idents!($seg, _SELECTOR).write(0)?;
113+
concat_idents!($seg, _BASE).write(0)?;
114+
concat_idents!($seg, _LIMIT).write(0xffff)?;
115+
concat_idents!($seg, _ACCESS_RIGHTS).write($access_rights)?;
116+
}};
117+
}
118+
119+
set_guest_segment!(ES, 0x93); // 16-bit, present, data, read/write, accessed
120+
set_guest_segment!(CS, 0x9b); // 16-bit, present, code, exec/read, accessed
121+
set_guest_segment!(SS, 0x93);
122+
set_guest_segment!(DS, 0x93);
123+
set_guest_segment!(FS, 0x93);
124+
set_guest_segment!(GS, 0x93);
125+
set_guest_segment!(TR, 0x8b); // present, system, 32-bit TSS busy
126+
set_guest_segment!(LDTR, 0x82); // present, system, LDT
127+
128+
VmcsGuestNW::GDTR_BASE.write(0)?;
129+
VmcsGuest32::GDTR_LIMIT.write(0xffff)?;
130+
VmcsGuestNW::IDTR_BASE.write(0)?;
131+
VmcsGuest32::IDTR_LIMIT.write(0xffff)?;
132+
133+
VmcsGuestNW::CR3.write(0)?;
134+
VmcsGuestNW::DR7.write(0x400)?;
135+
VmcsGuestNW::RSP.write(0)?;
136+
VmcsGuestNW::RIP.write(0)?; // TODO
137+
VmcsGuestNW::RFLAGS.write(0x2)?;
138+
VmcsGuestNW::PENDING_DBG_EXCEPTIONS.write(0)?;
139+
VmcsGuestNW::IA32_SYSENTER_ESP.write(0)?;
140+
VmcsGuestNW::IA32_SYSENTER_EIP.write(0)?;
141+
VmcsGuest32::IA32_SYSENTER_CS.write(0)?;
142+
143+
VmcsGuest32::INTERRUPTIBILITY_STATE.write(0)?;
144+
VmcsGuest32::ACTIVITY_STATE.write(0)?;
145+
VmcsGuest32::VMX_PREEMPTION_TIMER_VALUE.write(0)?;
146+
147+
VmcsGuest64::LINK_PTR.write(u64::MAX)?; // SDM Vol. 3C, Section 24.4.2
148+
VmcsGuest64::IA32_DEBUGCTL.write(0)?;
149+
VmcsGuest64::IA32_PAT.write(Msr::IA32_PAT.read())?;
150+
VmcsGuest64::IA32_EFER.write(0)?;
151+
Ok(())
152+
}
153+
154+
fn setup_vmcs_control(&mut self) -> RvmResult {
155+
// Intercept NMI, pass-through external interrupts.
156+
use super::vmcs::controls::*;
157+
use PinbasedControls as PinCtrl;
158+
vmcs::set_control(
159+
VmcsControl32::PINBASED_EXEC_CONTROLS,
160+
Msr::IA32_VMX_TRUE_PINBASED_CTLS,
161+
Msr::IA32_VMX_PINBASED_CTLS.read() as u32,
162+
PinCtrl::NMI_EXITING.bits(),
163+
0,
164+
)?;
165+
166+
// Activate secondary controls, disable CR3 load/store interception.
167+
use PrimaryControls as CpuCtrl;
168+
vmcs::set_control(
169+
VmcsControl32::PRIMARY_PROCBASED_EXEC_CONTROLS,
170+
Msr::IA32_VMX_TRUE_PROCBASED_CTLS,
171+
Msr::IA32_VMX_PROCBASED_CTLS.read() as u32,
172+
CpuCtrl::SECONDARY_CONTROLS.bits(),
173+
(CpuCtrl::CR3_LOAD_EXITING | CpuCtrl::CR3_STORE_EXITING).bits(),
174+
)?;
175+
176+
// Enable RDTSCP, INVPCID.
177+
use SecondaryControls as CpuCtrl2;
178+
vmcs::set_control(
179+
VmcsControl32::SECONDARY_PROCBASED_EXEC_CONTROLS,
180+
Msr::IA32_VMX_PROCBASED_CTLS2,
181+
0,
182+
(CpuCtrl2::ENABLE_RDTSCP | CpuCtrl2::ENABLE_INVPCID).bits(),
183+
0,
184+
)?;
185+
186+
// Switch to 64-bit host, switch IA32_PAT/IA32_EFER on VM exit.
187+
use ExitControls as ExitCtrl;
188+
vmcs::set_control(
189+
VmcsControl32::VMEXIT_CONTROLS,
190+
Msr::IA32_VMX_TRUE_EXIT_CTLS,
191+
Msr::IA32_VMX_EXIT_CTLS.read() as u32,
192+
(ExitCtrl::HOST_ADDRESS_SPACE_SIZE
193+
| ExitCtrl::SAVE_IA32_PAT
194+
| ExitCtrl::LOAD_IA32_PAT
195+
| ExitCtrl::SAVE_IA32_EFER
196+
| ExitCtrl::LOAD_IA32_EFER)
197+
.bits(),
198+
0,
199+
)?;
200+
201+
// Load guest IA32_PAT/IA32_EFER on VM entry.
202+
use EntryControls as EntryCtrl;
203+
vmcs::set_control(
204+
VmcsControl32::VMENTRY_CONTROLS,
205+
Msr::IA32_VMX_TRUE_ENTRY_CTLS,
206+
Msr::IA32_VMX_ENTRY_CTLS.read() as u32,
207+
(EntryCtrl::LOAD_IA32_PAT | EntryCtrl::LOAD_IA32_EFER).bits(),
208+
0,
209+
)?;
210+
211+
// No MSR switches if hypervisor doesn't use and there is only one vCPU.
212+
VmcsControl32::VMEXIT_MSR_STORE_COUNT.write(0)?;
213+
VmcsControl32::VMEXIT_MSR_LOAD_COUNT.write(0)?;
214+
VmcsControl32::VMENTRY_MSR_LOAD_COUNT.write(0)?;
215+
216+
// Pass-through exceptions, I/O instructions, and MSR read/write.
217+
VmcsControl32::EXCEPTION_BITMAP.write(0)?;
218+
VmcsControl64::IO_BITMAP_A_ADDR.write(0)?;
219+
VmcsControl64::IO_BITMAP_B_ADDR.write(0)?;
220+
VmcsControl64::MSR_BITMAPS_ADDR.write(0)?; // TODO
221+
Ok(())
222+
}
223+
}
224+
225+
impl<H: RvmHal> Drop for VmxVcpu<H> {
226+
fn drop(&mut self) {
227+
unsafe { vmx::vmclear(self.vmcs.phys_addr() as u64).unwrap() };
228+
info!("[RVM] dropped VmxVcpu(vmcs: {:#x})", self.vmcs.phys_addr());
229+
}
230+
}
231+
232+
fn get_tr_base(tr: SegmentSelector, gdt: &DescriptorTablePointer<u64>) -> u64 {
233+
let index = tr.index() as usize;
234+
let table_len = (gdt.limit as usize + 1) / core::mem::size_of::<u64>();
235+
let table = unsafe { core::slice::from_raw_parts(gdt.base, table_len) };
236+
let entry = table[index];
237+
if entry & (1 << 47) != 0 {
238+
// present
239+
let base_low = entry.get_bits(16..40) | entry.get_bits(56..64) << 24;
240+
let base_high = table[index + 1] & 0xffff_ffff;
241+
base_low | base_high << 32
242+
} else {
243+
// no present
244+
0
245+
}
246+
}
247+
248+
impl<H: RvmHal> Debug for VmxVcpu<H> {
249+
fn fmt(&self, f: &mut Formatter) -> Result {
250+
(|| -> RvmResult<Result> {
251+
Ok(f.debug_struct("VmxVcpu")
252+
.field("guest_regs", &self.guest_regs)
253+
.field("rip", &VmcsGuestNW::RIP.read()?)
254+
.field("rsp", &VmcsGuestNW::RSP.read()?)
255+
.field("rflags", &VmcsGuestNW::RFLAGS.read()?)
256+
.field("cr0", &VmcsGuestNW::CR0.read()?)
257+
.field("cr3", &VmcsGuestNW::CR3.read()?)
258+
.field("cr4", &VmcsGuestNW::CR4.read()?)
259+
.field("cs", &VmcsGuest16::CS_SELECTOR.read()?)
260+
.field("fs_base", &VmcsGuestNW::FS_BASE.read()?)
261+
.field("gs_base", &VmcsGuestNW::GS_BASE.read()?)
262+
.field("tss", &VmcsGuest16::TR_SELECTOR.read()?)
263+
.finish())
264+
})()
265+
.unwrap()
266+
}
267+
}

0 commit comments

Comments
 (0)