Skip to content

Commit 7687541

Browse files
committed
Step 6-2: virtualize APIC timer
1 parent 5867c8d commit 7687541

13 files changed

Lines changed: 549 additions & 10 deletions

File tree

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
Let's write an x86 hypervisor in Rust from scratch!
44

5+
## Features
6+
7+
* Lightweight enough, only 3K+ LoC
8+
* Supported guest OS: [NimbOS](https://github.com/equation314/nimbos)
9+
* Guest/host memory isolation with nested paging
10+
* Device emulation:
11+
+ serial port I/O
12+
+ APIC timer
13+
* Currently, only supports single core single vCPU and single guest
14+
515
## Install Build Dependencies
616

717
Install [cargo-binutils](https://github.com/rust-embedded/cargo-binutils) to use `rust-objcopy` and `rust-objdump` tools:
@@ -70,3 +80,7 @@ build_mode = release
7080
log_level = warn
7181
......
7282
```
83+
84+
## Documents
85+
86+
* [in Chinese](https://github.com/equation314/RVM-Tutorial/wiki)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//! Emulated Local APIC. (SDM Vol. 3A, Chapter 10)
2+
3+
#![allow(dead_code)]
4+
5+
use rvm::{RvmError, RvmResult, RvmVcpu};
6+
7+
type Vcpu = RvmVcpu<crate::hv::hal::RvmHalImpl>;
8+
9+
/// ID register.
10+
const APICID: u32 = 0x2;
11+
/// Version register.
12+
const VERSION: u32 = 0x3;
13+
/// EOI register.
14+
const EOI: u32 = 0xB;
15+
/// Logical Destination Register.
16+
const LDR: u32 = 0xD;
17+
/// Spurious Interrupt Vector register.
18+
const SIVR: u32 = 0xF;
19+
/// Interrupt Command register.
20+
const ICR: u32 = 0x30;
21+
/// LVT Timer Interrupt register.
22+
const LVT_TIMER: u32 = 0x32;
23+
/// LVT Thermal Sensor Interrupt register.
24+
const LVT_THERMAL: u32 = 0x33;
25+
/// LVT Performance Monitor register.
26+
const LVT_PMI: u32 = 0x34;
27+
/// LVT LINT0 register.
28+
const LVT_LINT0: u32 = 0x35;
29+
/// LVT LINT1 register.
30+
const LVT_LINT1: u32 = 0x36;
31+
/// LVT Error register.
32+
const LVT_ERR: u32 = 0x37;
33+
/// Initial Count register.
34+
const INIT_COUNT: u32 = 0x38;
35+
/// Current Count register.
36+
const CUR_COUNT: u32 = 0x39;
37+
/// Divide Configuration register.
38+
const DIV_CONF: u32 = 0x3E;
39+
40+
pub struct VirtLocalApic;
41+
42+
impl VirtLocalApic {
43+
pub const fn msr_range() -> core::ops::Range<u32> {
44+
0x800..0x840
45+
}
46+
47+
pub fn rdmsr(vcpu: &mut Vcpu, msr: u32) -> RvmResult<u64> {
48+
Self::read(vcpu, msr - 0x800)
49+
}
50+
51+
pub fn wrmsr(vcpu: &mut Vcpu, msr: u32, value: u64) -> RvmResult {
52+
Self::write(vcpu, msr - 0x800, value)
53+
}
54+
}
55+
56+
impl VirtLocalApic {
57+
fn read(vcpu: &mut Vcpu, offset: u32) -> RvmResult<u64> {
58+
let apic_timer = vcpu.apic_timer_mut();
59+
match offset {
60+
SIVR => Ok(0x1ff), // SDM Vol. 3A, Section 10.9, Figure 10-23 (with Software Enable bit)
61+
LVT_THERMAL | LVT_PMI | LVT_LINT0 | LVT_LINT1 | LVT_ERR => {
62+
Ok(0x1_0000) // SDM Vol. 3A, Section 10.5.1, Figure 10-8 (with Mask bit)
63+
}
64+
LVT_TIMER => Ok(apic_timer.lvt_timer() as u64),
65+
INIT_COUNT => Ok(apic_timer.initial_count() as u64),
66+
DIV_CONF => Ok(apic_timer.divide() as u64),
67+
CUR_COUNT => Ok(apic_timer.current_counter() as u64),
68+
_ => Err(RvmError::Unsupported),
69+
}
70+
}
71+
72+
fn write(vcpu: &mut Vcpu, offset: u32, value: u64) -> RvmResult {
73+
if offset != ICR && (value >> 32) != 0 {
74+
return Err(RvmError::InvalidParam); // all registers except ICR are 32-bits
75+
}
76+
let apic_timer = vcpu.apic_timer_mut();
77+
match offset {
78+
EOI => {
79+
if value != 0 {
80+
Err(RvmError::InvalidParam) // write a non-zero value causes #GP
81+
} else {
82+
Ok(())
83+
}
84+
}
85+
SIVR | LVT_THERMAL | LVT_PMI | LVT_LINT0 | LVT_LINT1 | LVT_ERR => {
86+
Ok(()) // ignore these register writes
87+
}
88+
LVT_TIMER => apic_timer.set_lvt_timer(value as u32),
89+
INIT_COUNT => apic_timer.set_initial_count(value as u32),
90+
DIV_CONF => apic_timer.set_divide(value as u32),
91+
_ => Err(RvmError::Unsupported),
92+
}
93+
}
94+
}

hypervisor/src/hv/device_emu/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
mod i8259_pic;
2+
mod lapic;
23
mod uart16550;
34

45
use alloc::{sync::Arc, vec, vec::Vec};
56

7+
pub use self::lapic::VirtLocalApic;
8+
69
pub trait PortIoDevice: Send + Sync {
710
fn port_range(&self) -> core::ops::Range<u16>;
811
fn read(&self, port: u16, access_size: u8) -> rvm::RvmResult<u32>;

hypervisor/src/hv/hal.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use rvm::{HostPhysAddr, HostVirtAddr, RvmHal, RvmVcpu};
22

33
use super::vmexit;
4+
use crate::arch::timer;
45
use crate::mm::{address, frame};
56

67
pub struct RvmHalImpl;
@@ -25,4 +26,8 @@ impl RvmHal for RvmHalImpl {
2526
fn vmexit_handler(vcpu: &mut RvmVcpu<Self>) {
2627
vmexit::vmexit_handler(vcpu).unwrap()
2728
}
29+
30+
fn current_time_nanos() -> u64 {
31+
timer::ticks_to_nanos(timer::current_ticks())
32+
}
2833
}

hypervisor/src/hv/vmexit.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
1-
use super::device_emu;
1+
use super::device_emu::{self, VirtLocalApic};
22
use super::hal::RvmHalImpl;
33
use rvm::arch::{VmxExitInfo, VmxExitReason};
44
use rvm::{RvmError, RvmResult, RvmVcpu};
55

66
type Vcpu = RvmVcpu<RvmHalImpl>;
77

88
const VM_EXIT_INSTR_LEN_CPUID: u8 = 2;
9+
const VM_EXIT_INSTR_LEN_RDMSR: u8 = 2;
10+
const VM_EXIT_INSTR_LEN_WRMSR: u8 = 2;
911
const VM_EXIT_INSTR_LEN_VMCALL: u8 = 3;
1012

13+
fn handle_external_interrupt(vcpu: &mut Vcpu) -> RvmResult {
14+
let int_info = vcpu.interrupt_exit_info()?;
15+
trace!("VM-exit: external interrupt: {:#x?}", int_info);
16+
assert!(int_info.valid);
17+
crate::arch::handle_irq(int_info.vector);
18+
Ok(())
19+
}
20+
1121
fn handle_cpuid(vcpu: &mut Vcpu) -> RvmResult {
1222
use raw_cpuid::{cpuid, CpuIdResult};
1323

@@ -118,6 +128,55 @@ fn handle_io_instruction(vcpu: &mut Vcpu, exit_info: &VmxExitInfo) -> RvmResult
118128
Ok(())
119129
}
120130

131+
fn handle_msr_read(vcpu: &mut Vcpu) -> RvmResult {
132+
let msr = vcpu.regs().rcx as u32;
133+
134+
use x86::msr::*;
135+
let res = if msr == IA32_APIC_BASE {
136+
let mut apic_base = unsafe { rdmsr(IA32_APIC_BASE) };
137+
apic_base |= 1 << 11 | 1 << 10; // enable xAPIC and x2APIC
138+
Ok(apic_base)
139+
} else if VirtLocalApic::msr_range().contains(&msr) {
140+
VirtLocalApic::rdmsr(vcpu, msr)
141+
} else {
142+
Err(RvmError::Unsupported)
143+
};
144+
145+
if let Ok(value) = res {
146+
debug!("VM exit: RDMSR({:#x}) -> {:#x}", msr, value);
147+
vcpu.regs_mut().rax = value & 0xffff_ffff;
148+
vcpu.regs_mut().rdx = value >> 32;
149+
} else {
150+
panic!("Failed to handle RDMSR({:#x}): {:?}", msr, res);
151+
}
152+
vcpu.advance_rip(VM_EXIT_INSTR_LEN_RDMSR)?;
153+
Ok(())
154+
}
155+
156+
fn handle_msr_write(vcpu: &mut Vcpu) -> RvmResult {
157+
let msr = vcpu.regs().rcx as u32;
158+
let value = (vcpu.regs().rax & 0xffff_ffff) | (vcpu.regs().rdx << 32);
159+
debug!("VM exit: WRMSR({:#x}) <- {:#x}", msr, value);
160+
161+
use x86::msr::*;
162+
let res = if msr == IA32_APIC_BASE {
163+
Ok(()) // ignore
164+
} else if VirtLocalApic::msr_range().contains(&msr) {
165+
VirtLocalApic::wrmsr(vcpu, msr, value)
166+
} else {
167+
Err(RvmError::Unsupported)
168+
};
169+
170+
if res.is_err() {
171+
panic!(
172+
"Failed to handle WRMSR({:#x}) <- {:#x}: {:?}",
173+
msr, value, res
174+
);
175+
}
176+
vcpu.advance_rip(VM_EXIT_INSTR_LEN_WRMSR)?;
177+
Ok(())
178+
}
179+
121180
fn handle_ept_violation(vcpu: &Vcpu, guest_rip: usize) -> RvmResult {
122181
let fault_info = vcpu.nested_page_fault_info()?;
123182
panic!(
@@ -135,9 +194,13 @@ pub fn vmexit_handler(vcpu: &mut Vcpu) -> RvmResult {
135194
}
136195

137196
let res = match exit_info.exit_reason {
197+
VmxExitReason::EXTERNAL_INTERRUPT => handle_external_interrupt(vcpu),
198+
VmxExitReason::INTERRUPT_WINDOW => vcpu.set_interrupt_window(false),
138199
VmxExitReason::CPUID => handle_cpuid(vcpu),
139200
VmxExitReason::VMCALL => handle_hypercall(vcpu),
140201
VmxExitReason::IO_INSTRUCTION => handle_io_instruction(vcpu, &exit_info),
202+
VmxExitReason::MSR_READ => handle_msr_read(vcpu),
203+
VmxExitReason::MSR_WRITE => handle_msr_write(vcpu),
141204
VmxExitReason::EPT_VIOLATION => handle_ept_violation(vcpu, exit_info.guest_rip),
142205
_ => panic!(
143206
"Unhandled VM-Exit reason {:?}:\n{:#x?}",

rvm/src/arch/x86_64/lapic.rs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use bit_field::BitField;
2+
use core::marker::PhantomData;
3+
4+
use crate::{RvmHal, RvmResult};
5+
6+
const APIC_FREQ_MHZ: u64 = 1000; // 1000 MHz
7+
const APIC_CYCLE_NANOS: u64 = 1000 / APIC_FREQ_MHZ;
8+
9+
/// Local APIC timer modes.
10+
#[derive(Debug, Copy, Clone)]
11+
#[repr(u8)]
12+
#[allow(dead_code)]
13+
pub enum TimerMode {
14+
/// Timer only fires once.
15+
OneShot = 0b00,
16+
/// Timer fires periodically.
17+
Periodic = 0b01,
18+
/// Timer fires at an absolute time.
19+
TscDeadline = 0b10,
20+
}
21+
22+
/// A virtual local APIC timer. (SDM Vol. 3C, Section 10.5.4)
23+
pub struct ApicTimer<H: RvmHal> {
24+
lvt_timer_bits: u32,
25+
divide_shift: u8,
26+
initial_count: u32,
27+
last_start_ns: u64,
28+
deadline_ns: u64,
29+
_phantom: PhantomData<H>,
30+
}
31+
32+
impl<H: RvmHal> ApicTimer<H> {
33+
pub(crate) const fn new() -> Self {
34+
Self {
35+
lvt_timer_bits: 0x1_0000, // masked
36+
divide_shift: 0,
37+
initial_count: 0,
38+
last_start_ns: 0,
39+
deadline_ns: 0,
40+
_phantom: PhantomData,
41+
}
42+
}
43+
44+
/// Check if an interrupt generated. if yes, update it's states.
45+
pub fn check_interrupt(&mut self) -> bool {
46+
if self.deadline_ns == 0 {
47+
false
48+
} else if H::current_time_nanos() >= self.deadline_ns {
49+
if self.is_periodic() {
50+
self.deadline_ns += self.interval_ns();
51+
} else {
52+
self.deadline_ns = 0;
53+
}
54+
!self.is_masked()
55+
} else {
56+
false
57+
}
58+
}
59+
60+
/// Whether the timer interrupt is masked.
61+
pub const fn is_masked(&self) -> bool {
62+
self.lvt_timer_bits & (1 << 16) != 0
63+
}
64+
65+
/// Whether the timer mode is periodic.
66+
pub const fn is_periodic(&self) -> bool {
67+
let timer_mode = (self.lvt_timer_bits >> 17) & 0b11;
68+
timer_mode == TimerMode::Periodic as _
69+
}
70+
71+
/// The timer interrupt vector number.
72+
pub const fn vector(&self) -> u8 {
73+
(self.lvt_timer_bits & 0xff) as u8
74+
}
75+
76+
/// LVT Timer Register. (SDM Vol. 3A, Section 10.5.1, Figure 10-8)
77+
pub const fn lvt_timer(&self) -> u32 {
78+
self.lvt_timer_bits
79+
}
80+
81+
/// Divide Configuration Register. (SDM Vol. 3A, Section 10.5.4, Figure 10-10)
82+
pub const fn divide(&self) -> u32 {
83+
let dcr = self.divide_shift.wrapping_sub(1) as u32 & 0b111;
84+
(dcr & 0b11) | ((dcr & 0b100) << 1)
85+
}
86+
87+
/// Initial Count Register.
88+
pub const fn initial_count(&self) -> u32 {
89+
self.initial_count
90+
}
91+
92+
/// Current Count Register.
93+
pub fn current_counter(&self) -> u32 {
94+
let elapsed_ns = H::current_time_nanos() - self.last_start_ns;
95+
let elapsed_cycles = (elapsed_ns / APIC_CYCLE_NANOS) >> self.divide_shift;
96+
if self.is_periodic() {
97+
self.initial_count - (elapsed_cycles % self.initial_count as u64) as u32
98+
} else if elapsed_cycles < self.initial_count as u64 {
99+
self.initial_count - elapsed_cycles as u32
100+
} else {
101+
0
102+
}
103+
}
104+
105+
/// Set LVT Timer Register.
106+
pub fn set_lvt_timer(&mut self, bits: u32) -> RvmResult {
107+
let timer_mode = bits.get_bits(17..19);
108+
if timer_mode == TimerMode::TscDeadline as _ {
109+
return rvm_err!(Unsupported); // TSC deadline mode was not supported
110+
} else if timer_mode == 0b11 {
111+
return rvm_err!(InvalidParam); // reserved
112+
}
113+
self.lvt_timer_bits = bits;
114+
self.start_timer();
115+
Ok(())
116+
}
117+
118+
/// Set Initial Count Register.
119+
pub fn set_initial_count(&mut self, initial: u32) -> RvmResult {
120+
self.initial_count = initial;
121+
self.start_timer();
122+
Ok(())
123+
}
124+
125+
/// Set Divide Configuration Register.
126+
pub fn set_divide(&mut self, dcr: u32) -> RvmResult {
127+
let shift = (dcr & 0b11) | ((dcr & 0b1000) >> 1);
128+
self.divide_shift = (shift + 1) as u8 & 0b111;
129+
self.start_timer();
130+
Ok(())
131+
}
132+
133+
const fn interval_ns(&self) -> u64 {
134+
(self.initial_count as u64 * APIC_CYCLE_NANOS) << self.divide_shift
135+
}
136+
137+
fn start_timer(&mut self) {
138+
if self.initial_count != 0 {
139+
self.last_start_ns = H::current_time_nanos();
140+
self.deadline_ns = self.last_start_ns + self.interval_ns();
141+
} else {
142+
self.deadline_ns = 0;
143+
}
144+
}
145+
}

0 commit comments

Comments
 (0)