diff options
author | Colin Downs-Razouk <colindr@google.com> | 2020-05-26 08:43:18 -0700 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2020-06-10 16:33:35 +0000 |
commit | 2a0ce34f31533b8e6e8c7b7ed1a55990702958ac (patch) | |
tree | 62aead8a36749319ce206d479a16b909a5e164d8 | |
parent | 55f21f743481dc44da6457757358262940b81286 (diff) | |
download | crosvm-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.rs | 7 | ||||
-rw-r--r-- | devices/src/irqchip/kvm/x86_64.rs | 187 | ||||
-rw-r--r-- | hypervisor/src/kvm/mod.rs | 28 | ||||
-rw-r--r-- | hypervisor/src/kvm/x86_64.rs | 124 | ||||
-rw-r--r-- | hypervisor/src/x86_64.rs | 2 |
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, } |