summary refs log tree commit diff
diff options
context:
space:
mode:
authorColin Downs-Razouk <colindr@google.com>2020-05-26 08:43:18 -0700
committerCommit Bot <commit-bot@chromium.org>2020-06-10 16:33:35 +0000
commit2a0ce34f31533b8e6e8c7b7ed1a55990702958ac (patch)
tree62aead8a36749319ce206d479a16b909a5e164d8
parent55f21f743481dc44da6457757358262940b81286 (diff)
downloadcrosvm-2a0ce34f31533b8e6e8c7b7ed1a55990702958ac.tar
crosvm-2a0ce34f31533b8e6e8c7b7ed1a55990702958ac.tar.gz
crosvm-2a0ce34f31533b8e6e8c7b7ed1a55990702958ac.tar.bz2
crosvm-2a0ce34f31533b8e6e8c7b7ed1a55990702958ac.tar.lz
crosvm-2a0ce34f31533b8e6e8c7b7ed1a55990702958ac.tar.xz
crosvm-2a0ce34f31533b8e6e8c7b7ed1a55990702958ac.tar.zst
crosvm-2a0ce34f31533b8e6e8c7b7ed1a55990702958ac.zip
devices: irqchip: KvmKernelIrqchip x86_64 impl
Implemented get/set_pic/ioapic/pit functions for the KvmKernelIrqchip.
Added respective functions on KvmVm for interacting with the underlying
KVM API.

Added associated tests for get/set functions.

BUG=chromium:1077058
TEST=ran devices tests and added get/set function tests

Change-Id: I66a29828fe2f1fbdf54d7325656a003ac09e36d0
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2219422
Reviewed-by: Udam Saini <udam@google.com>
Reviewed-by: Stephen Barber <smbarber@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Colin Downs-Razouk <colindr@google.com>
-rw-r--r--devices/src/irqchip/kvm/mod.rs7
-rw-r--r--devices/src/irqchip/kvm/x86_64.rs187
-rw-r--r--hypervisor/src/kvm/mod.rs28
-rw-r--r--hypervisor/src/kvm/x86_64.rs124
-rw-r--r--hypervisor/src/x86_64.rs2
5 files changed, 330 insertions, 18 deletions
diff --git a/devices/src/irqchip/kvm/mod.rs b/devices/src/irqchip/kvm/mod.rs
index bdc9d3f..a5fd89f 100644
--- a/devices/src/irqchip/kvm/mod.rs
+++ b/devices/src/irqchip/kvm/mod.rs
@@ -19,15 +19,18 @@ use crate::IrqChip;
 ///
 /// This implementation will use the KVM API to create and configure the in-kernel irqchip.
 pub struct KvmKernelIrqChip {
-    _vm: KvmVm,
+    vm: KvmVm,
     vcpus: Arc<Mutex<Vec<Option<KvmVcpu>>>>,
 }
 
 impl KvmKernelIrqChip {
     /// Construct a new KvmKernelIrqchip.
     pub fn new(vm: KvmVm, num_vcpus: usize) -> Result<KvmKernelIrqChip> {
+        // TODO (colindr): this constructor needs aarch64 vs x86_64 implementations because we
+        //  want to use vm.create_device instead of create_irq_chip on aarch64
+        vm.create_irq_chip()?;
         Ok(KvmKernelIrqChip {
-            _vm: vm,
+            vm,
             vcpus: Arc::new(Mutex::new((0..num_vcpus).map(|_| None).collect())),
         })
     }
diff --git a/devices/src/irqchip/kvm/x86_64.rs b/devices/src/irqchip/kvm/x86_64.rs
index 6f59935..acede53 100644
--- a/devices/src/irqchip/kvm/x86_64.rs
+++ b/devices/src/irqchip/kvm/x86_64.rs
@@ -4,30 +4,30 @@
 
 use hypervisor::kvm::KvmVcpu;
 use hypervisor::{IoapicState, LapicState, PicSelect, PicState, PitState};
-
+use kvm_sys::*;
 use sys_util::Result;
 
 use crate::{Bus, IrqChipX86_64, KvmKernelIrqChip};
 
 impl IrqChipX86_64<KvmVcpu> for KvmKernelIrqChip {
     /// Get the current state of the PIC
-    fn get_pic_state(&self, _select: PicSelect) -> Result<PicState> {
-        unimplemented!("get_pic_state for KvmKernelIrqChip is not yet implemented");
+    fn get_pic_state(&self, select: PicSelect) -> Result<PicState> {
+        Ok(PicState::from(&self.vm.get_pic_state(select)?))
     }
 
     /// Set the current state of the PIC
-    fn set_pic_state(&mut self, _select: PicSelect, _state: &PicState) -> Result<()> {
-        unimplemented!("set_pic_state for KvmKernelIrqChip is not yet implemented");
+    fn set_pic_state(&mut self, select: PicSelect, state: &PicState) -> Result<()> {
+        self.vm.set_pic_state(select, &kvm_pic_state::from(state))
     }
 
     /// Get the current state of the IOAPIC
     fn get_ioapic_state(&self) -> Result<IoapicState> {
-        unimplemented!("get_ioapic_state for KvmKernelIrqChip is not yet implemented");
+        Ok(IoapicState::from(&self.vm.get_ioapic_state()?))
     }
 
     /// Set the current state of the IOAPIC
-    fn set_ioapic_state(&mut self, _state: &IoapicState) -> Result<()> {
-        unimplemented!("set_ioapic_state for KvmKernelIrqChip is not yet implemented");
+    fn set_ioapic_state(&mut self, state: &IoapicState) -> Result<()> {
+        self.vm.set_ioapic_state(&kvm_ioapic_state::from(state))
     }
 
     /// Get the current state of the specified VCPU's local APIC
@@ -41,17 +41,176 @@ impl IrqChipX86_64<KvmVcpu> for KvmKernelIrqChip {
     }
 
     /// Create a PIT (Programmable Interval Timer) for this VM.
+    /// The KvmKernelIrqchip creates the PIT by calling the KVM_CREATE_PIT2 KVM API. The
+    /// io_bus is not used in this case because KVM handles intercepting port-mapped io intended
+    /// for the PIT.
     fn create_pit(&mut self, _io_bus: &mut Bus) -> Result<()> {
-        unimplemented!("create_pit for KvmKernelIrqChip is not yet implemented");
+        self.vm.create_pit()
     }
 
-    /// Retrieves the state of the PIT.
+    /// Retrieves the state of the PIT. Gets the pit state via the KVM API.
     fn get_pit(&self) -> Result<PitState> {
-        unimplemented!("get_pit for KvmKernelIrqChip is not yet implemented");
+        Ok(PitState::from(&self.vm.get_pit_state()?))
+    }
+
+    /// Sets the state of the PIT. Sets the pit state via the KVM API.
+    fn set_pit(&mut self, state: &PitState) -> Result<()> {
+        self.vm.set_pit_state(&kvm_pit_state2::from(state))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use hypervisor::kvm::{Kvm, KvmVm};
+    use sys_util::GuestMemory;
+
+    use crate::irqchip::{IrqChip, IrqChipX86_64, KvmKernelIrqChip};
+    use crate::Bus;
+
+    use hypervisor::{PicSelect, Vm, VmX86_64};
+
+    fn get_chip() -> (KvmKernelIrqChip, KvmVm) {
+        let kvm = Kvm::new().expect("failed to instantiate Kvm");
+        let mem = GuestMemory::new(&[]).unwrap();
+        let vm = KvmVm::new(&kvm, mem).expect("failed tso instantiate vm");
+        let vcpu = vm.create_vcpu(0).expect("failed to instantiate vcpu");
+
+        let mut chip = KvmKernelIrqChip::new(vm.try_clone().expect("failed to clone vm"), 1)
+            .expect("failed to instantiate KvmKernelIrqChip");
+
+        chip.add_vcpu(0, vcpu).expect("failed to add vcpu");
+
+        (chip, vm)
     }
 
-    /// Sets the state of the PIT.
-    fn set_pit(&mut self, _state: &PitState) -> Result<()> {
-        unimplemented!("set_pit for KvmKernelIrqChip is not yet implemented");
+    #[test]
+    fn get_pic() {
+        let (chip, vm) = get_chip();
+
+        let state = chip
+            .get_pic_state(PicSelect::Primary)
+            .expect("could not get pic state");
+
+        // Default is that no irq lines are asserted
+        assert_eq!(state.irr, 0);
+
+        // Assert Irq Line 0
+        vm.set_irq_line(0, true).expect("could not set irq line");
+
+        let state = chip
+            .get_pic_state(PicSelect::Primary)
+            .expect("could not get pic state");
+
+        // Bit 0 should now be 1
+        assert_eq!(state.irr, 1);
+    }
+
+    #[test]
+    fn set_pic() {
+        let (mut chip, _) = get_chip();
+
+        let mut state = chip
+            .get_pic_state(PicSelect::Primary)
+            .expect("could not get pic state");
+
+        // set bits 0 and 1
+        state.irr = 3;
+
+        chip.set_pic_state(PicSelect::Primary, &state)
+            .expect("could not set the pic state");
+
+        let state = chip
+            .get_pic_state(PicSelect::Primary)
+            .expect("could not get pic state");
+
+        // Bits 1 and 0 should now be 1
+        assert_eq!(state.irr, 3);
+    }
+
+    #[test]
+    fn get_ioapic() {
+        let (chip, vm) = get_chip();
+
+        let state = chip.get_ioapic_state().expect("could not get ioapic state");
+
+        // Default is that no irq lines are asserted
+        assert_eq!(state.current_interrupt_level_bitmap, 0);
+
+        // Default routing entries has routes 0..24 routed to vectors 0..24
+        for i in 0..24 {
+            // when the ioapic is reset by kvm, it defaults to all zeroes except the
+            // interrupt mask is set to 1, which is bit 16
+            assert_eq!(state.redirect_table[i].get(0, 64), 1 << 16);
+        }
+
+        // Assert Irq Line 1
+        vm.set_irq_line(1, true).expect("could not set irq line");
+
+        let state = chip.get_ioapic_state().expect("could not get ioapic state");
+
+        // Bit 1 should now be 1
+        assert_eq!(state.current_interrupt_level_bitmap, 2);
+    }
+
+    #[test]
+    fn set_ioapic() {
+        let (mut chip, _) = get_chip();
+
+        let mut state = chip.get_ioapic_state().expect("could not get ioapic state");
+
+        // set a vector in the redirect table
+        state.redirect_table[2].set_vector(15);
+        // set the irq line status on that entry
+        state.current_interrupt_level_bitmap = 4;
+
+        chip.set_ioapic_state(&state)
+            .expect("could not set the ioapic state");
+
+        let state = chip.get_ioapic_state().expect("could not get ioapic state");
+
+        // verify that get_ioapic_state returns what we set
+        assert_eq!(state.redirect_table[2].get_vector(), 15);
+        assert_eq!(state.current_interrupt_level_bitmap, 4);
+    }
+
+    #[test]
+    fn get_pit() {
+        let (mut chip, _) = get_chip();
+        let mut io_bus = Bus::new();
+        chip.create_pit(&mut io_bus).expect("failed to create pit");
+
+        let state = chip.get_pit().expect("failed to get pit state");
+
+        assert_eq!(state.flags, 0);
+        // assert reset state of pit
+        for i in 0..3 {
+            // initial count of 0 sets it to 0x10000;
+            assert_eq!(state.channels[i].count, 0x10000);
+            assert_eq!(state.channels[i].mode, 0xff);
+            assert_eq!(state.channels[i].gate, i != 2);
+        }
+    }
+
+    #[test]
+    fn set_pit() {
+        let (mut chip, _) = get_chip();
+        let mut io_bus = Bus::new();
+        chip.create_pit(&mut io_bus).expect("failed to create pit");
+
+        let mut state = chip.get_pit().expect("failed to get pit state");
+
+        // set some values
+        state.channels[0].count = 500;
+        state.channels[0].mode = 1;
+
+        // Setting the pit should initialize the one-shot timer
+        chip.set_pit(&state).expect("failed to set pit state");
+
+        let state = chip.get_pit().expect("failed to get pit state");
+
+        // check the values we set
+        assert_eq!(state.channels[0].count, 500);
+        assert_eq!(state.channels[0].mode, 1);
     }
 }
diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs
index 5fdf124..bf87e3d 100644
--- a/hypervisor/src/kvm/mod.rs
+++ b/hypervisor/src/kvm/mod.rs
@@ -171,6 +171,34 @@ impl KvmVm {
     fn create_kvm_vcpu(&self, _id: usize) -> Result<KvmVcpu> {
         Ok(KvmVcpu {})
     }
+
+    /// Crates an in kernel interrupt controller.
+    ///
+    /// See the documentation on the KVM_CREATE_IRQCHIP ioctl.
+    pub fn create_irq_chip(&self) -> Result<()> {
+        // Safe because we know that our file is a VM fd and we verify the return result.
+        let ret = unsafe { ioctl(self, KVM_CREATE_IRQCHIP()) };
+        if ret == 0 {
+            Ok(())
+        } else {
+            errno_result()
+        }
+    }
+    /// Sets the level on the given irq to 1 if `active` is true, and 0 otherwise.
+    pub fn set_irq_line(&self, irq: u32, active: bool) -> Result<()> {
+        let mut irq_level = kvm_irq_level::default();
+        irq_level.__bindgen_anon_1.irq = irq;
+        irq_level.level = if active { 1 } else { 0 };
+
+        // Safe because we know that our file is a VM fd, we know the kernel will only read the
+        // correct amount of memory from our pointer, and we verify the return result.
+        let ret = unsafe { ioctl_with_ref(self, KVM_IRQ_LINE(), &irq_level) };
+        if ret == 0 {
+            Ok(())
+        } else {
+            errno_result()
+        }
+    }
 }
 
 impl Vm for KvmVm {
diff --git a/hypervisor/src/kvm/x86_64.rs b/hypervisor/src/kvm/x86_64.rs
index f3fbc59..eaa34cf 100644
--- a/hypervisor/src/kvm/x86_64.rs
+++ b/hypervisor/src/kvm/x86_64.rs
@@ -14,7 +14,7 @@ use sys_util::{
 use super::{Kvm, KvmVcpu, KvmVm};
 use crate::{
     ClockState, CpuId, CpuIdEntry, HypervisorX86_64, IoapicRedirectionTableEntry, IoapicState,
-    LapicState, PicState, PitChannelState, PitState, Regs, VcpuX86_64, VmX86_64,
+    LapicState, PicSelect, PicState, PitChannelState, PitState, Regs, VcpuX86_64, VmX86_64,
 };
 
 type KvmCpuId = kvm::CpuId;
@@ -93,6 +93,128 @@ impl KvmVm {
             errno_result()
         }
     }
+
+    /// Retrieves the state of given interrupt controller by issuing KVM_GET_IRQCHIP ioctl.
+    ///
+    /// Note that this call can only succeed after a call to `Vm::create_irq_chip`.
+    pub fn get_pic_state(&self, id: PicSelect) -> Result<kvm_pic_state> {
+        let mut irqchip_state = kvm_irqchip::default();
+        irqchip_state.chip_id = id as u32;
+        let ret = unsafe {
+            // Safe because we know our file is a VM fd, we know the kernel will only write
+            // correct amount of memory to our pointer, and we verify the return result.
+            ioctl_with_mut_ref(self, KVM_GET_IRQCHIP(), &mut irqchip_state)
+        };
+        if ret == 0 {
+            Ok(unsafe {
+                // Safe as we know that we are retrieving data related to the
+                // PIC (primary or secondary) and not IOAPIC.
+                irqchip_state.chip.pic
+            })
+        } else {
+            errno_result()
+        }
+    }
+
+    /// Sets the state of given interrupt controller by issuing KVM_SET_IRQCHIP ioctl.
+    ///
+    /// Note that this call can only succeed after a call to `Vm::create_irq_chip`.
+    pub fn set_pic_state(&self, id: PicSelect, state: &kvm_pic_state) -> Result<()> {
+        let mut irqchip_state = kvm_irqchip::default();
+        irqchip_state.chip_id = id as u32;
+        irqchip_state.chip.pic = *state;
+        // Safe because we know that our file is a VM fd, we know the kernel will only read
+        // correct amount of memory from our pointer, and we verify the return result.
+        let ret = unsafe { ioctl_with_ref(self, KVM_SET_IRQCHIP(), &irqchip_state) };
+        if ret == 0 {
+            Ok(())
+        } else {
+            errno_result()
+        }
+    }
+
+    /// Retrieves the state of IOAPIC by issuing KVM_GET_IRQCHIP ioctl.
+    ///
+    /// Note that this call can only succeed after a call to `Vm::create_irq_chip`.
+    pub fn get_ioapic_state(&self) -> Result<kvm_ioapic_state> {
+        let mut irqchip_state = kvm_irqchip::default();
+        irqchip_state.chip_id = 2;
+        let ret = unsafe {
+            // Safe because we know our file is a VM fd, we know the kernel will only write
+            // correct amount of memory to our pointer, and we verify the return result.
+            ioctl_with_mut_ref(self, KVM_GET_IRQCHIP(), &mut irqchip_state)
+        };
+        if ret == 0 {
+            Ok(unsafe {
+                // Safe as we know that we are retrieving data related to the
+                // IOAPIC and not PIC.
+                irqchip_state.chip.ioapic
+            })
+        } else {
+            errno_result()
+        }
+    }
+
+    /// Sets the state of IOAPIC by issuing KVM_SET_IRQCHIP ioctl.
+    ///
+    /// Note that this call can only succeed after a call to `Vm::create_irq_chip`.
+    pub fn set_ioapic_state(&self, state: &kvm_ioapic_state) -> Result<()> {
+        let mut irqchip_state = kvm_irqchip::default();
+        irqchip_state.chip_id = 2;
+        irqchip_state.chip.ioapic = *state;
+        // Safe because we know that our file is a VM fd, we know the kernel will only read
+        // correct amount of memory from our pointer, and we verify the return result.
+        let ret = unsafe { ioctl_with_ref(self, KVM_SET_IRQCHIP(), &irqchip_state) };
+        if ret == 0 {
+            Ok(())
+        } else {
+            errno_result()
+        }
+    }
+
+    /// Creates a PIT as per the KVM_CREATE_PIT2 ioctl.
+    ///
+    /// Note that this call can only succeed after a call to `Vm::create_irq_chip`.
+    pub fn create_pit(&self) -> Result<()> {
+        let pit_config = kvm_pit_config::default();
+        // Safe because we know that our file is a VM fd, we know the kernel will only read the
+        // correct amount of memory from our pointer, and we verify the return result.
+        let ret = unsafe { ioctl_with_ref(self, KVM_CREATE_PIT2(), &pit_config) };
+        if ret == 0 {
+            Ok(())
+        } else {
+            errno_result()
+        }
+    }
+
+    /// Retrieves the state of PIT by issuing KVM_GET_PIT2 ioctl.
+    ///
+    /// Note that this call can only succeed after a call to `Vm::create_pit`.
+    pub fn get_pit_state(&self) -> Result<kvm_pit_state2> {
+        // Safe because we know that our file is a VM fd, we know the kernel will only write
+        // correct amount of memory to our pointer, and we verify the return result.
+        let mut pit_state = unsafe { std::mem::zeroed() };
+        let ret = unsafe { ioctl_with_mut_ref(self, KVM_GET_PIT2(), &mut pit_state) };
+        if ret == 0 {
+            Ok(pit_state)
+        } else {
+            errno_result()
+        }
+    }
+
+    /// Sets the state of PIT by issuing KVM_SET_PIT2 ioctl.
+    ///
+    /// Note that this call can only succeed after a call to `Vm::create_pit`.
+    pub fn set_pit_state(&self, pit_state: &kvm_pit_state2) -> Result<()> {
+        // Safe because we know that our file is a VM fd, we know the kernel will only read
+        // correct amount of memory from our pointer, and we verify the return result.
+        let ret = unsafe { ioctl_with_ref(self, KVM_SET_PIT2(), pit_state) };
+        if ret == 0 {
+            Ok(())
+        } else {
+            errno_result()
+        }
+    }
 }
 
 impl VmX86_64 for KvmVm {
diff --git a/hypervisor/src/x86_64.rs b/hypervisor/src/x86_64.rs
index c311924..859ebff 100644
--- a/hypervisor/src/x86_64.rs
+++ b/hypervisor/src/x86_64.rs
@@ -339,6 +339,6 @@ pub struct PitChannelState {
     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.
+    /// Nanosecond timestamp of when the count value was loaded.
     pub count_load_time: u64,
 }