summary refs log tree commit diff
diff options
context:
space:
mode:
authorColin Downs-Razouk <colindr@google.com>2020-05-04 10:45:55 -0700
committerCommit Bot <commit-bot@chromium.org>2020-05-28 08:00:30 +0000
commitc31a7b9fc689854d0ce3d98a1ce0dd75e5977c2d (patch)
tree0362cbcbc9f8b50777792f6f3c20193ab2d472b1
parent1a9f2a5454481a511257625f985d503c45fd8246 (diff)
downloadcrosvm-c31a7b9fc689854d0ce3d98a1ce0dd75e5977c2d.tar
crosvm-c31a7b9fc689854d0ce3d98a1ce0dd75e5977c2d.tar.gz
crosvm-c31a7b9fc689854d0ce3d98a1ce0dd75e5977c2d.tar.bz2
crosvm-c31a7b9fc689854d0ce3d98a1ce0dd75e5977c2d.tar.lz
crosvm-c31a7b9fc689854d0ce3d98a1ce0dd75e5977c2d.tar.xz
crosvm-c31a7b9fc689854d0ce3d98a1ce0dd75e5977c2d.tar.zst
crosvm-c31a7b9fc689854d0ce3d98a1ce0dd75e5977c2d.zip
hypervisor: x86 irqchip structs
Hypervisor-agnostic structures for the pic, ioapic, lapic, and pit.
These are derived from existing structures in the pic, ioapic, and pit
implementations, as well as from the kvm_sys bindings.

Includes From implementations converting these structures to their
associated KVM structures. Also includes tests for these conversion
implementations.

BUG=chromium:1077058
TEST=added tests to convert kvm structures to hypervisor-agnostic
structures

Change-Id: Ie2f254bf2dba3aed755008296c00cb6a49f845fd
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2197716
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Colin Downs-Razouk <colindr@google.com>
Reviewed-by: Zach Reizner <zachr@chromium.org>
-rw-r--r--Cargo.lock2
-rw-r--r--hypervisor/Cargo.toml2
-rw-r--r--hypervisor/src/kvm/x86_64.rs384
-rw-r--r--hypervisor/src/x86_64.rs294
4 files changed, 679 insertions, 3 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 2443037..124d045 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -349,6 +349,8 @@ dependencies = [
 name = "hypervisor"
 version = "0.1.0"
 dependencies = [
+ "bit_field 0.1.0",
+ "enumn 0.1.0",
  "kvm 0.1.0",
  "kvm_sys 0.1.0",
  "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml
index 8f07d5d..db3a095 100644
--- a/hypervisor/Cargo.toml
+++ b/hypervisor/Cargo.toml
@@ -5,6 +5,8 @@ authors = ["The Chromium OS Authors"]
 edition = "2018"
 
 [dependencies]
+bit_field = { path = "../bit_field" }
+enumn = { path = "../enumn" }
 libc = "*"
 kvm = { path = "../kvm" }
 kvm_sys = { path = "../kvm_sys" }
diff --git a/hypervisor/src/kvm/x86_64.rs b/hypervisor/src/kvm/x86_64.rs
index 43d6c97..06774f4 100644
--- a/hypervisor/src/kvm/x86_64.rs
+++ b/hypervisor/src/kvm/x86_64.rs
@@ -2,12 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+use std::convert::TryInto;
+
 use kvm_sys::*;
 use libc::E2BIG;
 use sys_util::{ioctl_with_mut_ptr, Error, Result};
 
 use super::{Kvm, KvmVcpu, KvmVm};
-use crate::{CpuId, CpuIdEntry, HypervisorX86_64, Regs, VcpuX86_64, VmX86_64};
+use crate::{
+    CpuId, CpuIdEntry, HypervisorX86_64, IoapicRedirectionTableEntry, IoapicState, LapicState,
+    PicState, PitChannelState, PitState, Regs, VcpuX86_64, VmX86_64,
+};
 
 type KvmCpuId = kvm::CpuId;
 
@@ -93,11 +98,222 @@ impl VcpuX86_64 for KvmVcpu {
     }
 }
 
+impl From<&kvm_pic_state> for PicState {
+    fn from(item: &kvm_pic_state) -> Self {
+        PicState {
+            last_irr: item.last_irr,
+            irr: item.irr,
+            imr: item.imr,
+            isr: item.isr,
+            priority_add: item.priority_add,
+            irq_base: item.irq_base,
+            read_reg_select: item.read_reg_select != 0,
+            poll: item.poll != 0,
+            special_mask: item.special_mask != 0,
+            init_state: item.init_state.into(),
+            auto_eoi: item.auto_eoi != 0,
+            rotate_on_auto_eoi: item.rotate_on_auto_eoi != 0,
+            special_fully_nested_mode: item.special_fully_nested_mode != 0,
+            use_4_byte_icw: item.init4 != 0,
+            elcr: item.elcr,
+            elcr_mask: item.elcr_mask,
+        }
+    }
+}
+
+impl From<&PicState> for kvm_pic_state {
+    fn from(item: &PicState) -> Self {
+        kvm_pic_state {
+            last_irr: item.last_irr,
+            irr: item.irr,
+            imr: item.imr,
+            isr: item.isr,
+            priority_add: item.priority_add,
+            irq_base: item.irq_base,
+            read_reg_select: item.read_reg_select as u8,
+            poll: item.poll as u8,
+            special_mask: item.special_mask as u8,
+            init_state: item.init_state as u8,
+            auto_eoi: item.auto_eoi as u8,
+            rotate_on_auto_eoi: item.rotate_on_auto_eoi as u8,
+            special_fully_nested_mode: item.special_fully_nested_mode as u8,
+            init4: item.use_4_byte_icw as u8,
+            elcr: item.elcr,
+            elcr_mask: item.elcr_mask,
+        }
+    }
+}
+
+impl From<&kvm_ioapic_state> for IoapicState {
+    fn from(item: &kvm_ioapic_state) -> Self {
+        let mut state = IoapicState {
+            base_address: item.base_address,
+            ioregsel: item.ioregsel,
+            ioapicid: item.id,
+            current_interrupt_level_bitmap: item.irr,
+            redirect_table: [IoapicRedirectionTableEntry::default(); 24],
+        };
+        for (in_state, out_state) in item.redirtbl.iter().zip(state.redirect_table.iter_mut()) {
+            *out_state = in_state.into();
+        }
+        state
+    }
+}
+
+impl From<&IoapicRedirectionTableEntry> for kvm_ioapic_state__bindgen_ty_1 {
+    fn from(item: &IoapicRedirectionTableEntry) -> Self {
+        kvm_ioapic_state__bindgen_ty_1 {
+            // IoapicRedirectionTableEntry layout matches the exact bit layout of a hardware
+            // ioapic redirection table entry, so we can simply do a 64-bit copy
+            bits: item.get(0, 64),
+        }
+    }
+}
+
+impl From<&kvm_ioapic_state__bindgen_ty_1> for IoapicRedirectionTableEntry {
+    fn from(item: &kvm_ioapic_state__bindgen_ty_1) -> Self {
+        let mut entry = IoapicRedirectionTableEntry::default();
+        // Safe because the 64-bit layout of the IoapicRedirectionTableEntry matches the kvm_sys
+        // table entry layout
+        entry.set(0, 64, unsafe { item.bits as u64 });
+        entry
+    }
+}
+
+impl From<&IoapicState> for kvm_ioapic_state {
+    fn from(item: &IoapicState) -> Self {
+        let mut state = kvm_ioapic_state {
+            base_address: item.base_address,
+            ioregsel: item.ioregsel,
+            id: item.ioapicid,
+            irr: item.current_interrupt_level_bitmap,
+            ..Default::default()
+        };
+        for (in_state, out_state) in item.redirect_table.iter().zip(state.redirtbl.iter_mut()) {
+            *out_state = in_state.into();
+        }
+        state
+    }
+}
+
+impl From<&LapicState> for kvm_lapic_state {
+    fn from(item: &LapicState) -> Self {
+        let mut state = kvm_lapic_state::default();
+        // There are 64 lapic registers
+        for (reg, value) in item.regs.iter().enumerate() {
+            // Each lapic register is 16 bytes, but only the first 4 are used
+            let reg_offset = 16 * reg;
+            let sliceu8 = unsafe {
+                // This array is only accessed as parts of a u32 word, so interpret it as a u8 array.
+                // to_le_bytes() produces an array of u8, not i8(c_char).
+                std::mem::transmute::<&mut [i8], &mut [u8]>(
+                    &mut state.regs[reg_offset..reg_offset + 4],
+                )
+            };
+            sliceu8.copy_from_slice(&value.to_le_bytes());
+        }
+        state
+    }
+}
+
+impl From<&kvm_lapic_state> for LapicState {
+    fn from(item: &kvm_lapic_state) -> Self {
+        let mut state = LapicState { regs: [0; 64] };
+        // There are 64 lapic registers
+        for reg in 0..64 {
+            // Each lapic register is 16 bytes, but only the first 4 are used
+            let reg_offset = 16 * reg;
+            let bytes = unsafe {
+                // This array is only accessed as parts of a u32 word, so interpret it as a u8 array.
+                // from_le_bytes() only works on arrays of u8, not i8(c_char).
+                std::mem::transmute::<&[i8], &[u8]>(&item.regs[reg_offset..reg_offset + 4])
+            };
+            state.regs[reg] = u32::from_le_bytes(bytes.try_into().unwrap());
+        }
+        state
+    }
+}
+
+impl From<&PitState> for kvm_pit_state2 {
+    fn from(item: &PitState) -> Self {
+        kvm_pit_state2 {
+            channels: [
+                kvm_pit_channel_state::from(&item.channels[0]),
+                kvm_pit_channel_state::from(&item.channels[1]),
+                kvm_pit_channel_state::from(&item.channels[2]),
+            ],
+            flags: item.flags,
+            ..Default::default()
+        }
+    }
+}
+
+impl From<&kvm_pit_state2> for PitState {
+    fn from(item: &kvm_pit_state2) -> Self {
+        PitState {
+            channels: [
+                PitChannelState::from(&item.channels[0]),
+                PitChannelState::from(&item.channels[1]),
+                PitChannelState::from(&item.channels[2]),
+            ],
+            flags: item.flags,
+        }
+    }
+}
+
+impl From<&PitChannelState> for kvm_pit_channel_state {
+    fn from(item: &PitChannelState) -> Self {
+        kvm_pit_channel_state {
+            count: item.count,
+            latched_count: item.latched_count,
+            count_latched: item.count_latched as u8,
+            status_latched: item.status_latched as u8,
+            status: item.status,
+            read_state: item.read_state as u8,
+            write_state: item.write_state as u8,
+            // kvm's write_latch only stores the low byte of the reload value
+            write_latch: item.reload_value as u8,
+            rw_mode: item.rw_mode as u8,
+            mode: item.mode,
+            bcd: item.bcd as u8,
+            gate: item.gate as u8,
+            count_load_time: item.count_load_time as i64,
+        }
+    }
+}
+
+impl From<&kvm_pit_channel_state> for PitChannelState {
+    fn from(item: &kvm_pit_channel_state) -> Self {
+        PitChannelState {
+            count: item.count,
+            latched_count: item.latched_count,
+            count_latched: item.count_latched.into(),
+            status_latched: item.status_latched != 0,
+            status: item.status,
+            read_state: item.read_state.into(),
+            write_state: item.write_state.into(),
+            // kvm's write_latch only stores the low byte of the reload value
+            reload_value: item.write_latch as u16,
+            rw_mode: item.rw_mode.into(),
+            mode: item.mode,
+            bcd: item.bcd != 0,
+            gate: item.gate != 0,
+            count_load_time: item.count_load_time as u64,
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
+    use crate::{
+        DeliveryMode, DeliveryStatus, DestinationMode, IoapicRedirectionTableEntry, IoapicState,
+        LapicState, PicInitState, PicState, PitChannelState, PitRWMode, PitRWState, PitState,
+        TriggerMode,
+    };
+    use kvm_sys::*;
+
     use super::Kvm;
     use crate::HypervisorX86_64;
-    use kvm_sys::*;
 
     #[test]
     fn get_supported_cpuid() {
@@ -121,4 +337,168 @@ mod tests {
             .unwrap();
         assert!(cpuid.cpu_id_entries.len() > 4);
     }
+
+    #[test]
+    fn pic_state() {
+        let state = PicState {
+            last_irr: 0b00000001,
+            irr: 0b00000010,
+            imr: 0b00000100,
+            isr: 0b00001000,
+            priority_add: 0b00010000,
+            irq_base: 0b00100000,
+            read_reg_select: false,
+            poll: true,
+            special_mask: true,
+            init_state: PicInitState::Icw3,
+            auto_eoi: true,
+            rotate_on_auto_eoi: false,
+            special_fully_nested_mode: true,
+            use_4_byte_icw: true,
+            elcr: 0b01000000,
+            elcr_mask: 0b10000000,
+        };
+
+        let kvm_state = kvm_pic_state::from(&state);
+
+        assert_eq!(kvm_state.last_irr, 0b00000001);
+        assert_eq!(kvm_state.irr, 0b00000010);
+        assert_eq!(kvm_state.imr, 0b00000100);
+        assert_eq!(kvm_state.isr, 0b00001000);
+        assert_eq!(kvm_state.priority_add, 0b00010000);
+        assert_eq!(kvm_state.irq_base, 0b00100000);
+        assert_eq!(kvm_state.read_reg_select, 0);
+        assert_eq!(kvm_state.poll, 1);
+        assert_eq!(kvm_state.special_mask, 1);
+        assert_eq!(kvm_state.init_state, 0b10);
+        assert_eq!(kvm_state.auto_eoi, 1);
+        assert_eq!(kvm_state.rotate_on_auto_eoi, 0);
+        assert_eq!(kvm_state.special_fully_nested_mode, 1);
+        assert_eq!(kvm_state.auto_eoi, 1);
+        assert_eq!(kvm_state.elcr, 0b01000000);
+        assert_eq!(kvm_state.elcr_mask, 0b10000000);
+
+        let orig_state = PicState::from(&kvm_state);
+        assert_eq!(state, orig_state);
+    }
+
+    #[test]
+    fn ioapic_state() {
+        let mut entry = IoapicRedirectionTableEntry::default();
+        // default entry should be 0
+        assert_eq!(entry.get(0, 64), 0);
+
+        // set some values on our entry
+        entry.set_vector(0b11111111);
+        entry.set_delivery_mode(DeliveryMode::SMI);
+        entry.set_dest_mode(DestinationMode::Physical);
+        entry.set_delivery_status(DeliveryStatus::Pending);
+        entry.set_polarity(1);
+        entry.set_remote_irr(true);
+        entry.set_trigger_mode(TriggerMode::Level);
+        entry.set_interrupt_mask(true);
+        entry.set_dest_id(0b10101010);
+
+        // Bit repr as:  destid-reserved--------------------------------flags----vector--
+        let bit_repr = 0b1010101000000000000000000000000000000000000000011111001011111111;
+        // where flags is [interrupt_mask(1), trigger_mode(Level=1), remote_irr(1), polarity(1),
+        //   delivery_status(Pending=1), dest_mode(Physical=0), delivery_mode(SMI=010)]
+
+        assert_eq!(entry.get(0, 64), bit_repr);
+
+        let state = IoapicState {
+            base_address: 1,
+            ioregsel: 2,
+            ioapicid: 4,
+            current_interrupt_level_bitmap: 8,
+            redirect_table: [entry; 24],
+        };
+
+        let kvm_state = kvm_ioapic_state::from(&state);
+        assert_eq!(kvm_state.base_address, 1);
+        assert_eq!(kvm_state.ioregsel, 2);
+        assert_eq!(kvm_state.id, 4);
+        assert_eq!(kvm_state.irr, 8);
+        assert_eq!(kvm_state.pad, 0);
+        // check our entries
+        for i in 0..24 {
+            assert_eq!(unsafe { kvm_state.redirtbl[i].bits }, bit_repr);
+        }
+
+        // compare with a conversion back
+        assert_eq!(state, IoapicState::from(&kvm_state));
+    }
+
+    #[test]
+    fn lapic_state() {
+        let mut state = LapicState { regs: [0; 64] };
+        // Apic id register, 4 bytes each with a different bit set
+        state.regs[2] = 1 | 2 << 8 | 4 << 16 | 8 << 24;
+
+        let kvm_state = kvm_lapic_state::from(&state);
+
+        // check little endian bytes in kvm_state
+        for i in 0..4 {
+            assert_eq!(
+                unsafe { std::mem::transmute::<i8, u8>(kvm_state.regs[32 + i]) } as u8,
+                2u8.pow(i as u32)
+            );
+        }
+
+        // Test converting back to a LapicState
+        assert_eq!(state, LapicState::from(&kvm_state));
+    }
+
+    #[test]
+    fn pit_state() {
+        let channel = PitChannelState {
+            count: 256,
+            latched_count: 512,
+            count_latched: PitRWState::LSB,
+            status_latched: false,
+            status: 7,
+            read_state: PitRWState::MSB,
+            write_state: PitRWState::Word1,
+            reload_value: 8,
+            rw_mode: PitRWMode::Both,
+            mode: 5,
+            bcd: false,
+            gate: true,
+            count_load_time: 1024,
+        };
+
+        let kvm_channel = kvm_pit_channel_state::from(&channel);
+
+        // compare the various field translations
+        assert_eq!(kvm_channel.count, 256);
+        assert_eq!(kvm_channel.latched_count, 512);
+        assert_eq!(kvm_channel.count_latched, 1);
+        assert_eq!(kvm_channel.status_latched, 0);
+        assert_eq!(kvm_channel.status, 7);
+        assert_eq!(kvm_channel.read_state, 2);
+        assert_eq!(kvm_channel.write_state, 4);
+        assert_eq!(kvm_channel.write_latch, 8);
+        assert_eq!(kvm_channel.rw_mode, 3);
+        assert_eq!(kvm_channel.mode, 5);
+        assert_eq!(kvm_channel.bcd, 0);
+        assert_eq!(kvm_channel.gate, 1);
+        assert_eq!(kvm_channel.count_load_time, 1024);
+
+        // convert back and compare
+        assert_eq!(channel, PitChannelState::from(&kvm_channel));
+
+        // convert the full pitstate
+        let state = PitState {
+            channels: [channel, channel, channel],
+            flags: 255,
+        };
+        let kvm_state = kvm_pit_state2::from(&state);
+
+        assert_eq!(kvm_state.flags, 255);
+
+        // compare a channel
+        assert_eq!(channel, PitChannelState::from(&kvm_state.channels[0]));
+        // convert back and compare
+        assert_eq!(state, PitState::from(&kvm_state));
+    }
 }
diff --git a/hypervisor/src/x86_64.rs b/hypervisor/src/x86_64.rs
index 87f0777..c311924 100644
--- a/hypervisor/src/x86_64.rs
+++ b/hypervisor/src/x86_64.rs
@@ -2,7 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-use sys_util::Result;
+use bit_field::*;
+use sys_util::{error, Result};
 
 use crate::{Hypervisor, Vcpu, Vm};
 
@@ -34,6 +35,8 @@ pub trait VcpuX86_64: Vcpu {
 /// about the hypervisor or vm. Information is returned in the eax, ebx, ecx and edx registers
 /// by the cpu for a given function and index/subfunction (passed into the cpu via the eax and ecx
 /// register respectively).
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
 pub struct CpuIdEntry {
     pub function: u32,
     pub index: u32,
@@ -50,3 +53,292 @@ pub struct CpuId {
 
 /// The state of a vcpu's general-purpose registers.
 pub struct Regs {}
+
+#[bitfield]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum DestinationMode {
+    Physical = 0,
+    Logical = 1,
+}
+
+#[bitfield]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum TriggerMode {
+    Edge = 0,
+    Level = 1,
+}
+
+#[bitfield]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum DeliveryMode {
+    Fixed = 0b000,
+    Lowest = 0b001,
+    SMI = 0b010,        // System management interrupt
+    RemoteRead = 0b011, // This is no longer supported by intel.
+    NMI = 0b100,        // Non maskable interrupt
+    Init = 0b101,
+    Startup = 0b110,
+    External = 0b111,
+}
+
+#[bitfield]
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct MsiAddressMessage {
+    pub reserved: BitField2,
+    #[bits = 1]
+    pub destination_mode: DestinationMode,
+    pub redirection_hint: BitField1,
+    pub reserved_2: BitField8,
+    pub destination_id: BitField8,
+    // According to Intel's implementation of MSI, these bits must always be 0xfee.
+    pub always_0xfee: BitField12,
+}
+
+#[bitfield]
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct MsiDataMessage {
+    pub vector: BitField8,
+    #[bits = 3]
+    pub delivery_mode: DeliveryMode,
+    pub reserved: BitField3,
+    pub level: BitField1,
+    #[bits = 1]
+    pub trigger: TriggerMode,
+    pub reserved2: BitField16,
+}
+
+#[bitfield]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum DeliveryStatus {
+    Idle = 0,
+    Pending = 1,
+}
+
+/// Represents a IOAPIC redirection table entry.
+#[bitfield]
+#[derive(Clone, Copy, Default, PartialEq, Eq)]
+pub struct IoapicRedirectionTableEntry {
+    vector: BitField8,
+    #[bits = 3]
+    delivery_mode: DeliveryMode,
+    #[bits = 1]
+    dest_mode: DestinationMode,
+    #[bits = 1]
+    delivery_status: DeliveryStatus,
+    polarity: BitField1,
+    remote_irr: bool,
+    #[bits = 1]
+    trigger_mode: TriggerMode,
+    interrupt_mask: bool, // true iff interrupts are masked.
+    reserved: BitField39,
+    dest_id: BitField8,
+}
+
+/// Number of pins on the IOAPIC.
+pub const NUM_IOAPIC_PINS: usize = 24;
+
+/// Represents the state of the IOAPIC.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub struct IoapicState {
+    /// base_address is the memory base address for this IOAPIC. It cannot be changed.
+    pub base_address: u64,
+    /// ioregsel register. Used for selecting which entry of the redirect table to read/write.
+    pub ioregsel: u32,
+    /// ioapicid register. Bits 24 - 27 contain the APIC ID for this device.
+    pub ioapicid: u32,
+    /// current_interrupt_level_bitmap represents a bitmap of the state of all of the irq lines
+    pub current_interrupt_level_bitmap: u32,
+    /// redirect_table contains the irq settings for each irq line
+    pub redirect_table: [IoapicRedirectionTableEntry; 24],
+}
+
+#[repr(C)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PicSelect {
+    Primary = 0,
+    Secondary = 1,
+}
+
+#[repr(C)]
+#[derive(enumn::N, Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PicInitState {
+    Icw1 = 0,
+    Icw2 = 1,
+    Icw3 = 2,
+    Icw4 = 3,
+}
+
+/// Convenience implementation for converting from a u8
+impl From<u8> for PicInitState {
+    fn from(item: u8) -> Self {
+        PicInitState::n(item).unwrap_or_else(|| {
+            error!("Invalid PicInitState {}, setting to 0", item);
+            PicInitState::Icw1
+        })
+    }
+}
+
+/// Represents the state of the PIC.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct PicState {
+    /// Edge detection.
+    pub last_irr: u8,
+    /// Interrupt Request Register.
+    pub irr: u8,
+    /// Interrupt Mask Register.
+    pub imr: u8,
+    /// Interrupt Service Register.
+    pub isr: u8,
+    /// Highest priority, for priority rotation.
+    pub priority_add: u8,
+    pub irq_base: u8,
+    pub read_reg_select: bool,
+    pub poll: bool,
+    pub special_mask: bool,
+    pub init_state: PicInitState,
+    pub auto_eoi: bool,
+    pub rotate_on_auto_eoi: bool,
+    pub special_fully_nested_mode: bool,
+    /// PIC takes either 3 or 4 bytes of initialization command word during
+    /// initialization. use_4_byte_icw is true if 4 bytes of ICW are needed.
+    pub use_4_byte_icw: bool,
+    /// "Edge/Level Control Registers", for edge trigger selection.
+    /// When a particular bit is set, the corresponding IRQ is in level-triggered mode. Otherwise it
+    /// is in edge-triggered mode.
+    pub elcr: u8,
+    pub elcr_mask: u8,
+}
+
+/// The LapicState represents the state of an x86 CPU's Local APIC.
+/// The Local APIC consists of 64 128-bit registers, but only the first 32-bits of each register
+/// can be used, so this structure only stores the first 32-bits of each register.
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct LapicState {
+    pub regs: [LapicRegister; 64],
+}
+
+pub type LapicRegister = u32;
+
+// rust arrays longer than 32 need custom implementations of Debug
+impl std::fmt::Debug for LapicState {
+    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+        self.regs[..].fmt(formatter)
+    }
+}
+
+// rust arrays longer than 32 need custom implementations of PartialEq
+impl PartialEq for LapicState {
+    fn eq(&self, other: &LapicState) -> bool {
+        self.regs[..] == other.regs[..]
+    }
+}
+
+// Lapic equality is reflexive, so we impl Eq
+impl Eq for LapicState {}
+
+/// The PitState represents the state of the PIT (aka the Programmable Interval Timer).
+/// The state is simply the state of it's three channels.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct PitState {
+    pub channels: [PitChannelState; 3],
+    /// Hypervisor-specific flags for setting the pit state.
+    pub flags: u32,
+}
+
+/// The PitRWMode enum represents the access mode of a PIT channel.
+/// Reads and writes to the Pit happen over Port-mapped I/O, which happens one byte at a time,
+/// but the count values and latch values are two bytes. So the access mode controls which of the
+/// two bytes will be read when.
+#[repr(C)]
+#[derive(enumn::N, Clone, Copy, Debug, PartialEq, Eq)]
+pub enum PitRWMode {
+    /// None mode means that no access mode has been set.
+    None = 0,
+    /// Least mode means all reads/writes will read/write the least significant byte.
+    Least = 1,
+    /// Most mode means all reads/writes will read/write the most significant byte.
+    Most = 2,
+    /// Both mode means first the least significant byte will be read/written, then the
+    /// next read/write will read/write the most significant byte.
+    Both = 3,
+}
+
+/// Convenience implementation for converting from a u8
+impl From<u8> for PitRWMode {
+    fn from(item: u8) -> Self {
+        PitRWMode::n(item).unwrap_or_else(|| {
+            error!("Invalid PitRWMode value {}, setting to 0", item);
+            PitRWMode::None
+        })
+    }
+}
+
+/// The PitRWState enum represents the state of reading to or writing from a channel.
+/// This is related to the PitRWMode, it mainly gives more detail about the state of the channel
+/// with respect to PitRWMode::Both.
+#[repr(C)]
+#[derive(enumn::N, Clone, Copy, Debug, PartialEq, Eq)]
+pub enum PitRWState {
+    /// None mode means that no access mode has been set.
+    None = 0,
+    /// LSB means that the channel is in PitRWMode::Least access mode.
+    LSB = 1,
+    /// MSB means that the channel is in PitRWMode::Most access mode.
+    MSB = 2,
+    /// Word0 means that the channel is in PitRWMode::Both mode, and the least sginificant byte
+    /// has not been read/written yet.
+    Word0 = 3,
+    /// Word1 means that the channel is in PitRWMode::Both mode and the least significant byte
+    /// has already been read/written, and the next byte to be read/written will be the most
+    /// significant byte.
+    Word1 = 4,
+}
+
+/// Convenience implementation for converting from a u8
+impl From<u8> for PitRWState {
+    fn from(item: u8) -> Self {
+        PitRWState::n(item).unwrap_or_else(|| {
+            error!("Invalid PitRWState value {}, setting to 0", item);
+            PitRWState::None
+        })
+    }
+}
+
+/// The PitChannelState represents the state of one of the PIT's three counters.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct PitChannelState {
+    /// The starting value for the counter.
+    pub count: u32,
+    /// Stores the channel count from the last time the count was latched.
+    pub latched_count: u16,
+    /// Indicates the PitRWState state of reading the latch value.
+    pub count_latched: PitRWState,
+    /// Indicates whether ReadBack status has been latched.
+    pub status_latched: bool,
+    /// Stores the channel status from the last time the status was latched. The status contains
+    /// information about the access mode of this channel, but changing those bits in the status
+    /// will not change the behavior of the pit.
+    pub status: u8,
+    /// Indicates the PitRWState state of reading the counter.
+    pub read_state: PitRWState,
+    /// Indicates the PitRWState state of writing the counter.
+    pub write_state: PitRWState,
+    /// Stores the value with which the counter was initialized. Counters are 16-
+    /// bit values with an effective range of 1-65536 (65536 represented by 0).
+    pub reload_value: u16,
+    /// The command access mode of this channel.
+    pub rw_mode: PitRWMode,
+    /// The operation mode of this channel.
+    pub mode: u8,
+    /// Whether or not we are in bcd mode. Not supported by KVM or crosvm's PIT implementation.
+    pub bcd: bool,
+    /// Value of the gate input pin. This only applies to channel 2.
+    pub gate: bool,
+    /// Guest boot nanosecond timestamp of when the count value was loaded.
+    pub count_load_time: u64,
+}