Skip to content

Commit 3396deb

Browse files
committed
Add basic support for ticking time in guest context
This requires an extensive set of changes. We must add support for a timer wheel that is serviced by the local apic timer. This requires support for per-core variables so we have a practical way of accessing the lapic and wheel from a variety of locations. We must also add a much more functional PIT implementation, so the guest can setup one-shot or periodic timers. However this change does _not_ add additional PIC support. Therefore, we just hard code the linux timer interrupt vector, as it will not be remapped in practice. The current intended functionality is that the hypervisor will mask interrupts and always service interrupts by handling the vmexit caused by any external interrupt. However, this may not work in practice, so this change adds better interrupt handling support.
1 parent 62d7165 commit 3396deb

File tree

26 files changed

+1157
-243
lines changed

26 files changed

+1157
-243
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ qemu: multiboot2 $(seabios)
4444

4545
.PHONY: qemu-debug
4646
qemu-debug: multiboot2-debug $(seabios)
47-
./scripts/mythril-run.sh $(multiboot2_debug_binary) \
47+
./scripts/mythril-run.sh $(multiboot2_binary) \
4848
-gdb tcp::1234 -S $(QEMU_EXTRA)
4949

5050
$(multiboot2_binary): $(mythril_src)

mythril_core/src/ap.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// A structure representing the data passed from the BSP to the AP
2+
/// through the ap_startup logic.
3+
#[repr(packed)]
4+
pub struct ApData {
5+
/// This AP's index in the sequential list of all AP's
6+
pub idx: u64,
7+
}

mythril_core/src/ap_startup.S

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,18 @@ ap_startup_64:
8888
; Load the stack provided by the bsp
8989
mov rsp, [AP_STACK_ADDR]
9090

91+
; Initialize FS with our per-core index, this will be used for the
92+
; fast per-core access later
93+
mov rdx, [AP_IDX]
94+
shl rdx, 3 ; Shift the AP_IDX to allow the RPL and TI bits to be 0
95+
mov fs, rdx
96+
97+
; See ap::ApData
98+
push qword [AP_IDX]
99+
100+
; Pass the info we've been given by the BSP up to the rust code
101+
mov rdi, rsp
102+
91103
; Acknowledge that the stack has been used
92104
mov byte [AP_READY], 1
93105

@@ -99,6 +111,10 @@ global AP_STACK_ADDR
99111
AP_STACK_ADDR:
100112
dq 0
101113

114+
global AP_IDX
115+
AP_IDX:
116+
dq 0
117+
102118
global AP_READY
103119
AP_READY:
104120
db 0

mythril_core/src/apic.rs

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#![deny(missing_docs)]
22

33
use crate::error::{Error, Result};
4+
use crate::time;
5+
use crate::{declare_per_core, get_per_core, get_per_core_mut};
46
use raw_cpuid::CpuId;
57
use x86::msr;
68

@@ -79,11 +81,35 @@ pub enum DeliveryMode {
7981
_Reserved2 = 0x07,
8082
}
8183

84+
declare_per_core! {
85+
static mut LOCAL_APIC: Option<LocalApic> = None;
86+
}
87+
88+
/// Obtain a reference to the current core's LocalApic
89+
pub fn get_local_apic() -> &'static LocalApic {
90+
get_per_core!(LOCAL_APIC)
91+
.as_ref()
92+
.expect("Attempt to get local APIC before initialization")
93+
}
94+
95+
/// Obtain a mutable reference to the current core's LocalApic
96+
///
97+
/// The caller must ensure that calling this function does not
98+
/// cause soundness violations such as holding two mutable
99+
/// references or a mutable and immutable reference.
100+
pub unsafe fn get_local_apic_mut() -> &'static mut LocalApic {
101+
get_per_core_mut!(LOCAL_APIC)
102+
.as_mut()
103+
.expect("Attempt to get local APIC before initialization")
104+
}
105+
82106
/// Structure defining the interface for a local x2APIC
83107
#[derive(Debug)]
84108
pub struct LocalApic {
85109
/// The raw value of the `IA32_APIC_BASE_MSR`
86110
base_reg: u64,
111+
112+
ticks_per_ms: u64,
87113
}
88114

89115
impl LocalApic {
@@ -93,7 +119,7 @@ impl LocalApic {
93119
///
94120
/// - The CPU does not support X2APIC
95121
/// - Unable to get the `cpuid`
96-
pub fn init() -> Result<LocalApic> {
122+
pub fn init() -> Result<&'static mut Self> {
97123
// Ensure the CPU supports x2apic
98124
let cpuid = CpuId::new();
99125
match cpuid.get_feature_info() {
@@ -123,7 +149,10 @@ impl LocalApic {
123149
// Fetch the new value of the APIC BASE MSR
124150
let base_reg = unsafe { msr::rdmsr(msr::IA32_APIC_BASE) };
125151

126-
let apic = LocalApic { base_reg };
152+
let mut apic = LocalApic {
153+
base_reg,
154+
ticks_per_ms: 0,
155+
};
127156

128157
// Enable the APIC in the Spurious Interrupt Vector Register
129158
unsafe {
@@ -149,7 +178,13 @@ impl LocalApic {
149178
// initial value of the MSR. For now we'll just stick to what the
150179
// spec says, but this should be investigated a bit further.
151180
apic.clear_esr();
152-
Ok(apic)
181+
182+
apic.calibrate_timer()
183+
.expect("Failed to calibrate APIC timer");
184+
185+
let lapic = get_per_core_mut!(LOCAL_APIC);
186+
*lapic = Some(apic);
187+
Ok(lapic.as_mut().unwrap())
153188
}
154189

155190
/// The APIC ID
@@ -200,7 +235,7 @@ impl LocalApic {
200235
}
201236

202237
/// Send a End Of Interrupt
203-
pub fn eoi(&self) {
238+
pub fn eoi(&mut self) {
204239
unsafe {
205240
msr::wrmsr(msr::IA32_X2APIC_EOI, 0x00);
206241
}
@@ -213,7 +248,7 @@ impl LocalApic {
213248

214249
/// Set the Interrupt Command Register
215250
pub fn send_ipi(
216-
&self,
251+
&mut self,
217252
dst: u32,
218253
dst_short: DstShorthand,
219254
trigger: TriggerMode,
@@ -236,10 +271,38 @@ impl LocalApic {
236271
}
237272

238273
/// Send a IPI to yourself
239-
pub fn self_ipi(&self, vector: u8) {
274+
pub fn self_ipi(&mut self, vector: u8) {
240275
// TODO(dlrobertson): Should we check for illegal vectors?
241276
unsafe {
242277
msr::wrmsr(msr::IA32_X2APIC_SELF_IPI, vector as u64);
243278
}
244279
}
280+
281+
fn calibrate_timer(&mut self) -> Result<()> {
282+
unsafe {
283+
let start_tick = 0xFFFFFFFF;
284+
msr::wrmsr(msr::IA32_X2APIC_DIV_CONF, 0x3); // timer divisor = 16
285+
msr::wrmsr(msr::IA32_X2APIC_INIT_COUNT, start_tick);
286+
time::busy_wait(core::time::Duration::from_millis(1));
287+
msr::wrmsr(msr::IA32_X2APIC_LVT_TIMER, 1 << 16); // Disable the timer
288+
let curr_tick = msr::rdmsr(msr::IA32_X2APIC_CUR_COUNT);
289+
self.ticks_per_ms = start_tick - curr_tick;
290+
}
291+
Ok(())
292+
}
293+
294+
/// Configure the timer for this local apic to generate an interrupt with
295+
/// the requested vector at the requested time. This will clear any outstanding
296+
/// apic interrupt.
297+
pub fn schedule_interrupt(&mut self, when: time::Instant, vector: u8) {
298+
//TODO: always round _up_ here to avoid the timer not actually being
299+
// expired when we receive the interrupt
300+
let micros = (when - time::now()).as_micros();
301+
let ticks = micros * self.ticks_per_ms as u128 / 1000;
302+
unsafe {
303+
msr::wrmsr(msr::IA32_X2APIC_DIV_CONF, 0x3); // timer divisor = 16
304+
msr::wrmsr(msr::IA32_X2APIC_LVT_TIMER, vector as u64);
305+
msr::wrmsr(msr::IA32_X2APIC_INIT_COUNT, ticks as u64);
306+
}
307+
}
245308
}

mythril_core/src/boot.S

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ GDT64: ; Global Descriptor Table (64-bit).
3030
dw 0 ; Base (low).
3131
db 0 ; Base (middle)
3232
db 10011010b ; Access (exec/read).
33-
db 10101111b ; Granularity, 64 bits flag, limit19:16.
33+
db 00100000b ; Granularity, 64 bits flag, limit19:16.
3434
db 0 ; Base (high).
3535
.data: equ $ - GDT64 ; The data descriptor.
3636
dw 0 ; Limit (low).
3737
dw 0 ; Base (low).
3838
db 0 ; Base (middle)
3939
db 10010010b ; Access (read/write).
40-
db 00000000b ; Granularity.
40+
db 00100000b ; Granularity.
4141
db 0 ; Base (high).
4242
.pointer: ; The GDT-pointer.
4343
dw $ - GDT64 - 1 ; Limit.

mythril_core/src/device/ignore.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ impl EmulatedDevice for IgnoredDevice {
2626
DeviceRegion::PortIo(240..=240),
2727
// IO delay port
2828
DeviceRegion::PortIo(128..=128),
29+
//TODO: don't know what this is yet
30+
DeviceRegion::PortIo(135..=135),
2931
]
3032
}
3133

mythril_core/src/device/mod.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -353,12 +353,12 @@ impl<'a> TryFrom<&'a [u8]> for PortWriteRequest<'a> {
353353
}
354354
}
355355

356-
impl<'a> TryInto<u8> for PortWriteRequest<'a> {
356+
impl<'a> TryFrom<PortWriteRequest<'a>> for u8 {
357357
type Error = Error;
358358

359-
fn try_into(self) -> Result<u8> {
360-
match self {
361-
Self::OneByte(val) => Ok(val[0]),
359+
fn try_from(value: PortWriteRequest<'a>) -> Result<Self> {
360+
match value {
361+
PortWriteRequest::OneByte(val) => Ok(val[0]),
362362
val => Err(Error::InvalidValue(format!(
363363
"Value {} cannot be converted to u8",
364364
val
@@ -367,12 +367,12 @@ impl<'a> TryInto<u8> for PortWriteRequest<'a> {
367367
}
368368
}
369369

370-
impl<'a> TryInto<u16> for PortWriteRequest<'a> {
370+
impl<'a> TryFrom<PortWriteRequest<'a>> for u16 {
371371
type Error = Error;
372372

373-
fn try_into(self) -> Result<u16> {
374-
match self {
375-
Self::TwoBytes(val) => Ok(u16::from_be_bytes(*val)),
373+
fn try_from(value: PortWriteRequest<'a>) -> Result<Self> {
374+
match value {
375+
PortWriteRequest::TwoBytes(val) => Ok(u16::from_be_bytes(*val)),
376376
val => Err(Error::InvalidValue(format!(
377377
"Value {} cannot be converted to u16",
378378
val
@@ -381,12 +381,12 @@ impl<'a> TryInto<u16> for PortWriteRequest<'a> {
381381
}
382382
}
383383

384-
impl<'a> TryInto<u32> for PortWriteRequest<'a> {
384+
impl<'a> TryFrom<PortWriteRequest<'a>> for u32 {
385385
type Error = Error;
386386

387-
fn try_into(self) -> Result<u32> {
388-
match self {
389-
Self::FourBytes(val) => Ok(u32::from_be_bytes(*val)),
387+
fn try_from(value: PortWriteRequest<'a>) -> Result<Self> {
388+
match value {
389+
PortWriteRequest::FourBytes(val) => Ok(u32::from_be_bytes(*val)),
390390
val => Err(Error::InvalidValue(format!(
391391
"Value {} cannot be converted to u32",
392392
val

mythril_core/src/device/pci.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ pub struct PciRootComplex {
152152

153153
impl PciRootComplex {
154154
const PCI_CONFIG_ADDRESS: Port = 0xcf8;
155+
const PCI_CONFIG_TYPE: Port = 0xcfb;
155156
const PCI_CONFIG_DATA: Port = 0xcfc;
156157
const PCI_CONFIG_DATA_MAX: Port = Self::PCI_CONFIG_DATA + 3;
157158

@@ -198,6 +199,7 @@ impl EmulatedDevice for PciRootComplex {
198199
DeviceRegion::PortIo(
199200
Self::PCI_CONFIG_DATA..=Self::PCI_CONFIG_DATA_MAX,
200201
),
202+
DeviceRegion::PortIo(Self::PCI_CONFIG_TYPE..=Self::PCI_CONFIG_TYPE),
201203
]
202204
}
203205
fn on_port_read(

mythril_core/src/device/pic.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ impl EmulatedDevice for Pic8259 {
5454
Self::PIC_MASTER_DATA => self.master_state.imr,
5555
Self::PIC_SLAVE_DATA => self.master_state.imr,
5656
_ => {
57-
info!("Read of PIC command port not yet supported");
5857
return Ok(());
5958
}
6059
};
@@ -70,19 +69,12 @@ impl EmulatedDevice for Pic8259 {
7069
) -> Result<()> {
7170
match port {
7271
Self::PIC_MASTER_DATA => {
73-
info!("Set master PIC data: {}", val);
7472
self.master_state.imr = val.try_into()?;
7573
}
7674
Self::PIC_SLAVE_DATA => {
77-
info!("Set slave PIC data: {}", val);
7875
self.master_state.imr = val.try_into()?;
7976
}
80-
port => {
81-
info!(
82-
"Write to PIC command port not yet supported (port 0x{:x} = {})",
83-
port, val
84-
);
85-
}
77+
_ => (),
8678
}
8779
Ok(())
8880
}

0 commit comments

Comments
 (0)