diff --git a/devices/src/irqchip/kvm/mod.rs b/devices/src/irqchip/kvm/mod.rs index d0fa6c8f97..2956b74ccb 100644 --- a/devices/src/irqchip/kvm/mod.rs +++ b/devices/src/irqchip/kvm/mod.rs @@ -213,56 +213,3 @@ impl IrqChip for KvmKernelIrqChip { } } } - -#[cfg(test)] -mod tests { - use hypervisor::kvm::Kvm; - use hypervisor::kvm::KvmVm; - use hypervisor::MPState; - use hypervisor::Vm; - #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] - use hypervisor::VmAArch64; - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - use hypervisor::VmX86_64; - use vm_memory::GuestMemory; - - use crate::irqchip::IrqChip; - use crate::irqchip::KvmKernelIrqChip; - - #[test] - fn create_kvm_kernel_irqchip() { - let kvm = Kvm::new().expect("failed to instantiate Kvm"); - let mem = GuestMemory::new(&[]).unwrap(); - let vm = KvmVm::new(&kvm, mem, Default::default()).expect("failed to instantiate vm"); - - let mut chip = KvmKernelIrqChip::new(vm.try_clone().expect("failed to clone vm"), 1) - .expect("failed to instantiate KvmKernelIrqChip"); - - let vcpu = vm.create_vcpu(0).expect("failed to instantiate vcpu"); - chip.add_vcpu(0, vcpu.as_vcpu()) - .expect("failed to add vcpu"); - } - - #[test] - fn mp_state() { - let kvm = Kvm::new().expect("failed to instantiate Kvm"); - let mem = GuestMemory::new(&[]).unwrap(); - let vm = KvmVm::new(&kvm, mem, Default::default()).expect("failed to instantiate vm"); - - let mut chip = KvmKernelIrqChip::new(vm.try_clone().expect("failed to clone vm"), 1) - .expect("failed to instantiate KvmKernelIrqChip"); - - let vcpu = vm.create_vcpu(0).expect("failed to instantiate vcpu"); - chip.add_vcpu(0, vcpu.as_vcpu()) - .expect("failed to add vcpu"); - - let state = chip.get_mp_state(0).expect("failed to get mp state"); - assert_eq!(state, MPState::Runnable); - - chip.set_mp_state(0, &MPState::Stopped) - .expect("failed to set mp state"); - - let state = chip.get_mp_state(0).expect("failed to get mp state"); - assert_eq!(state, MPState::Stopped); - } -} diff --git a/devices/src/irqchip/kvm/x86_64.rs b/devices/src/irqchip/kvm/x86_64.rs index 0cfbd91380..5ec86bea4e 100644 --- a/devices/src/irqchip/kvm/x86_64.rs +++ b/devices/src/irqchip/kvm/x86_64.rs @@ -302,7 +302,7 @@ impl KvmSplitIrqChip { /// Return true if there is a pending interrupt for the specified vcpu. For KvmSplitIrqChip /// this calls interrupt_requested on the pic. - fn interrupt_requested(&self, vcpu_id: usize) -> bool { + pub fn interrupt_requested(&self, vcpu_id: usize) -> bool { // Pic interrupts for the split irqchip only go to vcpu 0 if vcpu_id != 0 { return false; @@ -313,7 +313,7 @@ impl KvmSplitIrqChip { /// Check if the specified vcpu has any pending interrupts. Returns None for no interrupts, /// otherwise Some(u32) should be the injected interrupt vector. For KvmSplitIrqChip /// this calls get_external_interrupt on the pic. - fn get_external_interrupt(&self, vcpu_id: usize) -> Option { + pub fn get_external_interrupt(&self, vcpu_id: usize) -> Option { // Pic interrupts for the split irqchip only go to vcpu 0 if vcpu_id != 0 { return None; @@ -806,442 +806,3 @@ impl IrqChipX86_64 for KvmSplitIrqChip { true } } - -#[cfg(test)] -mod tests { - - use base::EventWaitResult; - use hypervisor::kvm::Kvm; - use hypervisor::IoapicRedirectionTableEntry; - use hypervisor::PitRWMode; - use hypervisor::TriggerMode; - use hypervisor::Vm; - use hypervisor::VmX86_64; - use resources::AddressRange; - use resources::SystemAllocator; - use resources::SystemAllocatorConfig; - use vm_memory::GuestMemory; - - use super::*; - use crate::irqchip::tests::*; - use crate::pci::CrosvmDeviceId; - use crate::DeviceId; - use crate::IrqChip; - - /// Helper function for setting up a KvmKernelIrqChip - fn get_kernel_chip() -> KvmKernelIrqChip { - let kvm = Kvm::new().expect("failed to instantiate Kvm"); - let mem = GuestMemory::new(&[]).unwrap(); - let vm = KvmVm::new(&kvm, mem, Default::default()).expect("failed tso instantiate vm"); - - let mut chip = KvmKernelIrqChip::new(vm.try_clone().expect("failed to clone vm"), 1) - .expect("failed to instantiate KvmKernelIrqChip"); - - let vcpu = vm.create_vcpu(0).expect("failed to instantiate vcpu"); - chip.add_vcpu(0, vcpu.as_vcpu()) - .expect("failed to add vcpu"); - - chip - } - - /// Helper function for setting up a KvmSplitIrqChip - fn get_split_chip() -> KvmSplitIrqChip { - let kvm = Kvm::new().expect("failed to instantiate Kvm"); - let mem = GuestMemory::new(&[]).unwrap(); - let vm = KvmVm::new(&kvm, mem, Default::default()).expect("failed tso instantiate vm"); - - let (_, device_tube) = Tube::pair().expect("failed to create irq tube"); - - let mut chip = KvmSplitIrqChip::new( - vm.try_clone().expect("failed to clone vm"), - 1, - device_tube, - None, - ) - .expect("failed to instantiate KvmKernelIrqChip"); - - let vcpu = vm.create_vcpu(0).expect("failed to instantiate vcpu"); - chip.add_vcpu(0, vcpu.as_vcpu()) - .expect("failed to add vcpu"); - chip - } - - #[test] - fn kernel_irqchip_pit_uses_speaker_port() { - let chip = get_kernel_chip(); - assert!(!chip.pit_uses_speaker_port()); - } - - #[test] - fn kernel_irqchip_get_pic() { - test_get_pic(get_kernel_chip()); - } - - #[test] - fn kernel_irqchip_set_pic() { - test_set_pic(get_kernel_chip()); - } - - #[test] - fn kernel_irqchip_get_ioapic() { - test_get_ioapic(get_kernel_chip()); - } - - #[test] - fn kernel_irqchip_set_ioapic() { - test_set_ioapic(get_kernel_chip()); - } - - #[test] - fn kernel_irqchip_get_pit() { - test_get_pit(get_kernel_chip()); - } - - #[test] - fn kernel_irqchip_set_pit() { - test_set_pit(get_kernel_chip()); - } - - #[test] - fn kernel_irqchip_get_lapic() { - test_get_lapic(get_kernel_chip()) - } - - #[test] - fn kernel_irqchip_set_lapic() { - test_set_lapic(get_kernel_chip()) - } - - #[test] - fn kernel_irqchip_route_irq() { - test_route_irq(get_kernel_chip()); - } - - #[test] - fn split_irqchip_get_pic() { - test_get_pic(get_split_chip()); - } - - #[test] - fn split_irqchip_set_pic() { - test_set_pic(get_split_chip()); - } - - #[test] - fn split_irqchip_get_ioapic() { - test_get_ioapic(get_split_chip()); - } - - #[test] - fn split_irqchip_set_ioapic() { - test_set_ioapic(get_split_chip()); - } - - #[test] - fn split_irqchip_get_pit() { - test_get_pit(get_split_chip()); - } - - #[test] - fn split_irqchip_set_pit() { - test_set_pit(get_split_chip()); - } - - #[test] - fn split_irqchip_route_irq() { - test_route_irq(get_split_chip()); - } - - #[test] - fn split_irqchip_pit_uses_speaker_port() { - let chip = get_split_chip(); - assert!(chip.pit_uses_speaker_port()); - } - - #[test] - fn split_irqchip_routes_conflict() { - let mut chip = get_split_chip(); - chip.route_irq(IrqRoute { - gsi: 5, - source: IrqSource::Msi { - address: 4276092928, - data: 0, - }, - }) - .expect("failed to set msi rout"); - // this second route should replace the first - chip.route_irq(IrqRoute { - gsi: 5, - source: IrqSource::Msi { - address: 4276092928, - data: 32801, - }, - }) - .expect("failed to set msi rout"); - } - - #[test] - fn irq_event_tokens() { - let mut chip = get_split_chip(); - let tokens = chip - .irq_event_tokens() - .expect("could not get irq_event_tokens"); - - // there should be one token on a fresh split irqchip, for the pit - assert_eq!(tokens.len(), 1); - assert_eq!(tokens[0].1.device_name, "userspace PIT"); - - // register another irq event - let evt = IrqEdgeEvent::new().expect("failed to create event"); - let source = IrqEventSource { - device_id: CrosvmDeviceId::Cmos.into(), - queue_id: 0, - device_name: "test".into(), - }; - chip.register_edge_irq_event(6, &evt, source) - .expect("failed to register irq event"); - - let tokens = chip - .irq_event_tokens() - .expect("could not get irq_event_tokens"); - - // now there should be two tokens - assert_eq!(tokens.len(), 2); - assert_eq!(tokens[0].1.device_name, "userspace PIT"); - assert_eq!( - tokens[1].1.device_id, - DeviceId::PlatformDeviceId(CrosvmDeviceId::Cmos) - ); - assert_eq!(tokens[1].2, *evt.get_trigger()); - } - - #[test] - fn finalize_devices() { - let mut chip = get_split_chip(); - - let mmio_bus = Bus::new(); - let io_bus = Bus::new(); - let mut resources = SystemAllocator::new( - SystemAllocatorConfig { - io: Some(AddressRange { - start: 0xc000, - end: 0xffff, - }), - low_mmio: AddressRange { - start: 0, - end: 2047, - }, - high_mmio: AddressRange { - start: 0x1_0000_0000, - end: 0x2_ffff_ffff, - }, - platform_mmio: None, - first_irq: 5, - }, - None, - &[], - ) - .expect("failed to create SystemAllocator"); - - // Set up a level-triggered interrupt line 1 - let evt = IrqLevelEvent::new().expect("failed to create event"); - - let source = IrqEventSource { - device_id: CrosvmDeviceId::Cmos.into(), - device_name: "test".into(), - queue_id: 0, - }; - let evt_index = chip - .register_level_irq_event(1, &evt, source) - .expect("failed to register irq event") - .expect("register_irq_event should not return None"); - - // Once we finalize devices, the pic/pit/ioapic should be attached to io and mmio busses - chip.finalize_devices(&mut resources, &io_bus, &mmio_bus) - .expect("failed to finalize devices"); - - // Should not be able to allocate an irq < 24 now - assert!(resources.allocate_irq().expect("failed to allocate irq") >= 24); - - // set PIT counter 2 to "SquareWaveGen"(aka 3) mode and "Both" access mode - io_bus.write(0x43, &[0b10110110]); - - let state = chip.get_pit().expect("failed to get pit state"); - assert_eq!(state.channels[2].mode, 3); - assert_eq!(state.channels[2].rw_mode, PitRWMode::Both); - - // ICW1 0x11: Edge trigger, cascade mode, ICW4 needed. - // ICW2 0x08: Interrupt vector base address 0x08. - // ICW3 0xff: Value written does not matter. - // ICW4 0x13: Special fully nested mode, auto EOI. - io_bus.write(0x20, &[0x11]); - io_bus.write(0x21, &[0x08]); - io_bus.write(0x21, &[0xff]); - io_bus.write(0x21, &[0x13]); - - let state = chip - .get_pic_state(PicSelect::Primary) - .expect("failed to get pic state"); - - // auto eoi and special fully nested mode should be turned on - assert!(state.auto_eoi); - assert!(state.special_fully_nested_mode); - - // Need to write to the irq event before servicing it - evt.trigger().expect("failed to write to event"); - - // if we assert irq line one, and then get the resulting interrupt, an auto-eoi should - // occur and cause the resample_event to be written to - chip.service_irq_event(evt_index) - .expect("failed to service irq"); - - assert!(chip.interrupt_requested(0)); - assert_eq!( - chip.get_external_interrupt(0) - .expect("failed to get external interrupt"), - // Vector is 9 because the interrupt vector base address is 0x08 and this is irq - // line 1 and 8+1 = 9 - 0x9 - ); - - // Clone resample event because read_timeout() needs a mutable reference. - let resample_evt = evt.get_resample().try_clone().unwrap(); - assert_eq!( - resample_evt - .wait_timeout(std::time::Duration::from_secs(1)) - .expect("failed to read_timeout"), - EventWaitResult::Signaled - ); - - // setup a ioapic redirection table entry 14 - let mut entry = IoapicRedirectionTableEntry::default(); - entry.set_vector(44); - - let irq_14_offset = 0x10 + 14 * 2; - mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_14_offset]); - mmio_bus.write( - IOAPIC_BASE_ADDRESS + 0x10, - &(entry.get(0, 32) as u32).to_ne_bytes(), - ); - mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_14_offset + 1]); - mmio_bus.write( - IOAPIC_BASE_ADDRESS + 0x10, - &(entry.get(32, 32) as u32).to_ne_bytes(), - ); - - let state = chip.get_ioapic_state().expect("failed to get ioapic state"); - - // redirection table entry 14 should have a vector of 44 - assert_eq!(state.redirect_table[14].get_vector(), 44); - } - - #[test] - fn get_external_interrupt() { - let mut chip = get_split_chip(); - assert!(!chip.interrupt_requested(0)); - - chip.service_irq(0, true).expect("failed to service irq"); - assert!(chip.interrupt_requested(0)); - - // Should return Some interrupt - assert_eq!( - chip.get_external_interrupt(0) - .expect("failed to get external interrupt"), - 0, - ); - - // interrupt is not requested twice - assert!(!chip.interrupt_requested(0)); - } - - #[test] - fn broadcast_eoi() { - let mut chip = get_split_chip(); - - let mmio_bus = Bus::new(); - let io_bus = Bus::new(); - let mut resources = SystemAllocator::new( - SystemAllocatorConfig { - io: Some(AddressRange { - start: 0xc000, - end: 0xffff, - }), - low_mmio: AddressRange { - start: 0, - end: 2047, - }, - high_mmio: AddressRange { - start: 0x1_0000_0000, - end: 0x2_ffff_ffff, - }, - platform_mmio: None, - first_irq: 5, - }, - None, - &[], - ) - .expect("failed to create SystemAllocator"); - - let source = IrqEventSource { - device_id: CrosvmDeviceId::Cmos.into(), - device_name: "test".into(), - queue_id: 0, - }; - // setup an event and a resample event for irq line 1 - let evt = IrqLevelEvent::new().expect("failed to create event"); - - chip.register_level_irq_event(1, &evt, source) - .expect("failed to register_level_irq_event"); - - // Once we finalize devices, the pic/pit/ioapic should be attached to io and mmio busses - chip.finalize_devices(&mut resources, &io_bus, &mmio_bus) - .expect("failed to finalize devices"); - - // setup a ioapic redirection table entry 1 with a vector of 123 - let mut entry = IoapicRedirectionTableEntry::default(); - entry.set_vector(123); - entry.set_trigger_mode(TriggerMode::Level); - - let irq_write_offset = 0x10 + 1 * 2; - mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_write_offset]); - mmio_bus.write( - IOAPIC_BASE_ADDRESS + 0x10, - &(entry.get(0, 32) as u32).to_ne_bytes(), - ); - mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_write_offset + 1]); - mmio_bus.write( - IOAPIC_BASE_ADDRESS + 0x10, - &(entry.get(32, 32) as u32).to_ne_bytes(), - ); - - // Assert line 1 - chip.service_irq(1, true).expect("failed to service irq"); - - // resample event should not be written to - assert_eq!( - evt.get_resample() - .wait_timeout(std::time::Duration::from_millis(10)) - .expect("failed to read_timeout"), - EventWaitResult::TimedOut - ); - - // irq line 1 should be asserted - let state = chip.get_ioapic_state().expect("failed to get ioapic state"); - assert_eq!(state.current_interrupt_level_bitmap, 1 << 1); - - // Now broadcast an eoi for vector 123 - chip.broadcast_eoi(123).expect("failed to broadcast eoi"); - - // irq line 1 should be deasserted - let state = chip.get_ioapic_state().expect("failed to get ioapic state"); - assert_eq!(state.current_interrupt_level_bitmap, 0); - - // resample event should be written to by ioapic - assert_eq!( - evt.get_resample() - .wait_timeout(std::time::Duration::from_millis(10)) - .expect("failed to read_timeout"), - EventWaitResult::Signaled - ); - } -} diff --git a/devices/src/irqchip/userspace.rs b/devices/src/irqchip/userspace.rs index 8a76917747..9a6d6433d1 100644 --- a/devices/src/irqchip/userspace.rs +++ b/devices/src/irqchip/userspace.rs @@ -92,13 +92,13 @@ const X86_CR0_INIT: u64 = X86_CR0_ET | X86_CR0_NW | X86_CR0_CD; /// An `IrqChip` with all interrupt devices emulated in userspace. `UserspaceIrqChip` works with /// any hypervisor, but only supports x86. pub struct UserspaceIrqChip { - vcpus: Arc>>>, + pub vcpus: Arc>>>, routes: Arc>, pit: Arc>, pic: Arc>, ioapic: Arc>, ioapic_pins: usize, - apics: Vec>>, + pub apics: Vec>>, // Condition variables used by wait_until_runnable. waiters: Vec>, // Raw descriptors of the apic Timers. @@ -240,7 +240,7 @@ impl UserspaceIrqChip { self.send_irq_to_apics(&Interrupt { dest, data }); } - fn send_irq_to_apic(&self, id: usize, irq: &InterruptData) { + pub fn send_irq_to_apic(&self, id: usize, irq: &InterruptData) { // id can come from the guest, so check bounds. if let Some(apic) = self.apics.get(id) { apic.lock().accept_irq(irq); @@ -256,7 +256,7 @@ impl UserspaceIrqChip { } /// Sends an interrupt to one or more APICs. Used for sending MSIs and IPIs. - fn send_irq_to_apics(&self, irq: &Interrupt) { + pub fn send_irq_to_apics(&self, irq: &Interrupt) { match irq.data.delivery { DeliveryMode::Fixed | DeliveryMode::Lowest | DeliveryMode::RemoteRead => {} _ => info!("UserspaceIrqChip received special irq: {:?}", irq), @@ -1026,890 +1026,3 @@ impl Display for TimerWorkerError { impl std::error::Error for TimerWorkerError {} type TimerWorkerResult = std::result::Result; - -// TODO(b/237977699): remove once test are re-enabled. -#[allow(unused)] -#[cfg(test)] -mod tests { - use std::os::raw::c_int; - use std::time::Duration; - use std::time::Instant; - - use base::EventWaitResult; - use hypervisor::CpuId; - use hypervisor::CpuIdEntry; - use hypervisor::DebugRegs; - use hypervisor::DestinationMode; - use hypervisor::Fpu; - use hypervisor::HypervHypercall; - use hypervisor::IoParams; - use hypervisor::IoapicRedirectionTableEntry; - use hypervisor::Level; - use hypervisor::PitRWMode; - use hypervisor::Register; - use hypervisor::Regs; - use hypervisor::Sregs; - use hypervisor::TriggerMode; - use hypervisor::Vcpu; - use hypervisor::VcpuExit; - use hypervisor::VcpuRunHandle; - use resources::AddressRange; - use resources::SystemAllocatorConfig; - use vm_memory::GuestAddress; - - use super::super::tests::*; - use super::super::DestinationShorthand; - use super::*; - - const APIC_ID: u64 = 0x20; - const TPR: u64 = 0x80; - const EOI: u64 = 0xB0; - const LOCAL_TIMER: u64 = 0x320; - const TIMER_INITIAL_COUNT: u64 = 0x380; - const TIMER_DIVIDE_CONTROL: u64 = 0x3E0; - const TIMER_MODE_PERIODIC: u32 = 1 << 17; - const TIMER_FREQ_DIVIDE_BY_1: u8 = 0b1011; - const TEST_SLEEP_DURATION: Duration = Duration::from_millis(50); - - /// Helper function for setting up a UserspaceIrqChip. - fn get_chip(num_vcpus: usize) -> UserspaceIrqChip { - get_chip_with_clock(num_vcpus, Arc::new(Mutex::new(Clock::new()))) - } - - fn get_chip_with_clock( - num_vcpus: usize, - clock: Arc>, - ) -> UserspaceIrqChip { - let (_, irq_tube) = Tube::pair().unwrap(); - let mut chip = - UserspaceIrqChip::::new_with_clock(num_vcpus, irq_tube, None, clock) - .expect("failed to instantiate UserspaceIrqChip"); - - for i in 0..num_vcpus { - let vcpu = FakeVcpu { - id: i, - requested: Arc::new(Mutex::new(false)), - ready: Arc::new(Mutex::new(true)), - injected: Arc::new(Mutex::new(None)), - }; - chip.add_vcpu(i, &vcpu).expect("failed to add vcpu"); - chip.apics[i].lock().set_enabled(true); - } - - chip - } - - /// Helper function for cloning vcpus from a UserspaceIrqChip. - fn get_vcpus(chip: &UserspaceIrqChip) -> Vec { - chip.vcpus - .lock() - .iter() - .map(|v| v.as_ref().unwrap().try_clone().unwrap()) - .collect() - } - - #[test] - fn set_pic() { - test_set_pic(get_chip(1)); - } - - #[test] - fn get_ioapic() { - test_get_ioapic(get_chip(1)); - } - - #[test] - fn set_ioapic() { - test_set_ioapic(get_chip(1)); - } - - #[test] - fn get_pit() { - test_get_pit(get_chip(1)); - } - - #[test] - fn set_pit() { - test_set_pit(get_chip(1)); - } - - #[test] - fn route_irq() { - test_route_irq(get_chip(1)); - } - - #[test] - fn pit_uses_speaker_port() { - let chip = get_chip(1); - assert!(chip.pit_uses_speaker_port()); - } - - #[test] - fn routes_conflict() { - let mut chip = get_chip(1); - chip.route_irq(IrqRoute { - gsi: 32, - source: IrqSource::Msi { - address: 4276092928, - data: 0, - }, - }) - .expect("failed to set msi rout"); - // this second route should replace the first - chip.route_irq(IrqRoute { - gsi: 32, - source: IrqSource::Msi { - address: 4276092928, - data: 32801, - }, - }) - .expect("failed to set msi rout"); - } - - #[test] - fn irq_event_tokens() { - let mut chip = get_chip(1); - let tokens = chip - .irq_event_tokens() - .expect("could not get irq_event_tokens"); - - // there should be one token on a fresh split irqchip, for the pit - assert_eq!(tokens.len(), 1); - assert_eq!(tokens[0].1.device_name, "userspace PIT"); - - // register another irq event - let evt = IrqEdgeEvent::new().expect("failed to create eventfd"); - let source = IrqEventSource { - device_id: CrosvmDeviceId::Cmos.into(), - queue_id: 0, - device_name: "test".to_owned(), - }; - chip.register_edge_irq_event(6, &evt, source) - .expect("failed to register irq event"); - - let tokens = chip - .irq_event_tokens() - .expect("could not get irq_event_tokens"); - - // now there should be two tokens - assert_eq!(tokens.len(), 2); - assert_eq!(tokens[0].1.device_name, "userspace PIT"); - assert_eq!( - tokens[1].1.device_id, - DeviceId::PlatformDeviceId(CrosvmDeviceId::Cmos) - ); - assert_eq!(tokens[1].2, evt.get_trigger().try_clone().unwrap()); - } - - // TODO(srichman): Factor out of UserspaceIrqChip and KvmSplitIrqChip. - #[test] - fn finalize_devices() { - let mut chip = get_chip(1); - - let mmio_bus = Bus::new(); - let io_bus = Bus::new(); - let mut resources = SystemAllocator::new( - SystemAllocatorConfig { - io: Some(AddressRange { - start: 0xc000, - end: 0xFFFF, - }), - low_mmio: AddressRange { - start: 0, - end: 2047, - }, - high_mmio: AddressRange { - start: 2048, - end: 6143, - }, - platform_mmio: None, - first_irq: 5, - }, - None, - &[], - ) - .expect("failed to create SystemAllocator"); - - // Setup an event and a resample event for irq line 1. - let evt = IrqLevelEvent::new().expect("failed to create event"); - - let source = IrqEventSource { - device_id: CrosvmDeviceId::Cmos.into(), - device_name: "test".to_owned(), - queue_id: 0, - }; - - let evt_index = chip - .register_level_irq_event(1, &evt, source) - .expect("failed to register_level_irq_event") - .expect("register_level_irq_event should not return None"); - - // Once we finalize devices, the pic/pit/ioapic should be attached to io and mmio busses. - chip.finalize_devices(&mut resources, &io_bus, &mmio_bus) - .expect("failed to finalize devices"); - - // Should not be able to allocate an irq < 24 now. - assert!(resources.allocate_irq().expect("failed to allocate irq") >= 24); - - // Set PIT counter 2 to "SquareWaveGen" (aka 3) mode and "Both" access mode. - io_bus.write(0x43, &[0b10110110]); - - let state = chip.get_pit().expect("failed to get pit state"); - assert_eq!(state.channels[2].mode, 3); - assert_eq!(state.channels[2].rw_mode, PitRWMode::Both); - - // ICW1 0x11: Edge trigger, cascade mode, ICW4 needed. - // ICW2 0x08: Interrupt vector base address 0x08. - // ICW3 0xff: Value written does not matter. - // ICW4 0x13: Special fully nested mode, auto EOI. - io_bus.write(0x20, &[0x11]); - io_bus.write(0x21, &[0x08]); - io_bus.write(0x21, &[0xff]); - io_bus.write(0x21, &[0x13]); - - let state = chip - .get_pic_state(PicSelect::Primary) - .expect("failed to get pic state"); - - // Auto eoi and special fully nested mode should be turned on. - assert!(state.auto_eoi); - assert!(state.special_fully_nested_mode); - - // Need to write to the irq event before servicing it. - evt.trigger().expect("failed to write to eventfd"); - - // If we assert irq line one, and then get the resulting interrupt, an auto-eoi should occur - // and cause the resample_event to be written to. - chip.service_irq_event(evt_index) - .expect("failed to service irq"); - - let vcpu = get_vcpus(&chip).remove(0); - chip.inject_interrupts(&vcpu).unwrap(); - assert_eq!( - vcpu.clear_injected(), - // Vector is 9 because the interrupt vector base address is 0x08 and this is irq line 1 - // and 8+1 = 9. - Some(0x9) - ); - - assert_eq!( - evt.get_resample() - .wait_timeout(std::time::Duration::from_secs(1)) - .expect("failed to read_timeout"), - EventWaitResult::Signaled - ); - - // Setup a ioapic redirection table entry 14. - let mut entry = IoapicRedirectionTableEntry::default(); - entry.set_vector(44); - - let irq_14_offset = 0x10 + 14 * 2; - mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_14_offset]); - mmio_bus.write( - IOAPIC_BASE_ADDRESS + 0x10, - &(entry.get(0, 32) as u32).to_ne_bytes(), - ); - mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_14_offset + 1]); - mmio_bus.write( - IOAPIC_BASE_ADDRESS + 0x10, - &(entry.get(32, 32) as u32).to_ne_bytes(), - ); - - let state = chip.get_ioapic_state().expect("failed to get ioapic state"); - - // Redirection table entry 14 should have a vector of 44. - assert_eq!(state.redirect_table[14].get_vector(), 44); - } - - #[test] - fn inject_pic_interrupt() { - let mut chip = get_chip(2); - let vcpus = get_vcpus(&chip); - - assert_eq!(vcpus[0].clear_injected(), None); - assert_eq!(vcpus[1].clear_injected(), None); - - chip.service_irq(0, true).expect("failed to service irq"); - - // Should not inject PIC interrupt for vcpu_id != 0. - chip.inject_interrupts(&vcpus[1]).unwrap(); - assert_eq!(vcpus[1].clear_injected(), None); - - // Should inject Some interrupt. - chip.inject_interrupts(&vcpus[0]).unwrap(); - assert_eq!(vcpus[0].clear_injected(), Some(0)); - - // Interrupt is not injected twice. - chip.inject_interrupts(&vcpus[0]).unwrap(); - assert_eq!(vcpus[0].clear_injected(), None); - } - - #[test] - fn inject_msi() { - let mut chip = get_chip(2); - let vcpus = get_vcpus(&chip); - - let evt = IrqEdgeEvent::new().unwrap(); - let source = IrqEventSource { - device_id: CrosvmDeviceId::Cmos.into(), - device_name: "test".to_owned(), - queue_id: 0, - }; - let event_idx = chip - .register_edge_irq_event(30, &evt, source) - .unwrap() - .unwrap(); - chip.route_irq(IrqRoute { - gsi: 30, - source: IrqSource::Msi { - address: 0xFEE01000, // physical addressing, send to apic 1 - data: 0x000000F1, // edge-triggered, fixed interrupt, vector 0xF1 - }, - }) - .unwrap(); - evt.trigger().unwrap(); - - assert!(!vcpus[0].window_requested()); - assert!(!vcpus[1].window_requested()); - chip.service_irq_event(event_idx).unwrap(); - assert!(!vcpus[0].window_requested()); - assert!(vcpus[1].window_requested()); - - chip.inject_interrupts(&vcpus[0]).unwrap(); - assert_eq!(vcpus[0].clear_injected(), None); - - vcpus[1].set_ready(false); - chip.inject_interrupts(&vcpus[1]).unwrap(); - assert_eq!(vcpus[1].clear_injected(), None); - vcpus[1].set_ready(true); - chip.inject_interrupts(&vcpus[1]).unwrap(); - assert_eq!(vcpus[1].clear_injected(), Some(0xF1)); - assert!(!vcpus[1].window_requested()); - } - - #[test] - fn lowest_priority_destination() { - let chip = get_chip(2); - let vcpus = get_vcpus(&chip); - - // Make vcpu 0 higher priority. - chip.write( - BusAccessInfo { - id: 0, - address: APIC_BASE_ADDRESS + TPR, - offset: TPR, - }, - &[0x10, 0, 0, 0], - ); - chip.send_irq_to_apics(&Interrupt { - dest: InterruptDestination { - source_id: 0, - dest_id: 0, - shorthand: DestinationShorthand::All, - mode: DestinationMode::Physical, - }, - data: InterruptData { - vector: 111, - delivery: DeliveryMode::Lowest, - trigger: TriggerMode::Edge, - level: Level::Deassert, - }, - }); - chip.inject_interrupts(&vcpus[0]).unwrap(); - chip.inject_interrupts(&vcpus[1]).unwrap(); - assert_eq!(vcpus[0].clear_injected(), None); - assert_eq!(vcpus[1].clear_injected(), Some(111)); - chip.write( - BusAccessInfo { - id: 1, - address: APIC_BASE_ADDRESS + EOI, - offset: EOI, - }, - &[0, 0, 0, 0], - ); - - // Make vcpu 1 higher priority. - chip.write( - BusAccessInfo { - id: 1, - address: APIC_BASE_ADDRESS + TPR, - offset: TPR, - }, - &[0x20, 0, 0, 0], - ); - chip.send_irq_to_apics(&Interrupt { - dest: InterruptDestination { - source_id: 0, - dest_id: 0, - shorthand: DestinationShorthand::All, - mode: DestinationMode::Physical, - }, - data: InterruptData { - vector: 222, - delivery: DeliveryMode::Lowest, - trigger: TriggerMode::Edge, - level: Level::Deassert, - }, - }); - chip.inject_interrupts(&vcpus[0]).unwrap(); - chip.inject_interrupts(&vcpus[1]).unwrap(); - assert_eq!(vcpus[0].clear_injected(), Some(222)); - assert_eq!(vcpus[1].clear_injected(), None); - } - - // TODO(srichman): Factor out of UserspaceIrqChip and KvmSplitIrqChip. - #[test] - fn broadcast_eoi() { - let mut chip = get_chip(1); - - let mmio_bus = Bus::new(); - let io_bus = Bus::new(); - let mut resources = SystemAllocator::new( - SystemAllocatorConfig { - io: Some(AddressRange { - start: 0xc000, - end: 0xFFFF, - }), - low_mmio: AddressRange { - start: 0, - end: 2047, - }, - high_mmio: AddressRange { - start: 2048, - end: 6143, - }, - platform_mmio: None, - first_irq: 5, - }, - None, - &[], - ) - .expect("failed to create SystemAllocator"); - - // setup an event for irq line 1 - let evt = IrqLevelEvent::new().expect("failed to create event"); - - let source = IrqEventSource { - device_id: CrosvmDeviceId::Cmos.into(), - device_name: "test".to_owned(), - queue_id: 0, - }; - - chip.register_level_irq_event(1, &evt, source) - .expect("failed to register_level_irq_event"); - - // Once we finalize devices, the pic/pit/ioapic should be attached to io and mmio busses - chip.finalize_devices(&mut resources, &io_bus, &mmio_bus) - .expect("failed to finalize devices"); - - // setup a ioapic redirection table entry 1 with a vector of 123 - let mut entry = IoapicRedirectionTableEntry::default(); - entry.set_vector(123); - entry.set_trigger_mode(TriggerMode::Level); - - let irq_write_offset = 0x10 + 1 * 2; - mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_write_offset]); - mmio_bus.write( - IOAPIC_BASE_ADDRESS + 0x10, - &(entry.get(0, 32) as u32).to_ne_bytes(), - ); - mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_write_offset + 1]); - mmio_bus.write( - IOAPIC_BASE_ADDRESS + 0x10, - &(entry.get(32, 32) as u32).to_ne_bytes(), - ); - - // Assert line 1 - chip.service_irq(1, true).expect("failed to service irq"); - - // resample event should not be written to - assert_eq!( - evt.get_resample() - .wait_timeout(std::time::Duration::from_millis(10)) - .expect("failed to read_timeout"), - EventWaitResult::TimedOut - ); - - // irq line 1 should be asserted - let state = chip.get_ioapic_state().expect("failed to get ioapic state"); - assert_eq!(state.current_interrupt_level_bitmap, 1 << 1); - - // Now broadcast an eoi for vector 123 - chip.broadcast_eoi(123).expect("failed to broadcast eoi"); - - // irq line 1 should be deasserted - let state = chip.get_ioapic_state().expect("failed to get ioapic state"); - assert_eq!(state.current_interrupt_level_bitmap, 0); - - // resample event should be written to by ioapic - assert_eq!( - evt.get_resample() - .wait_timeout(std::time::Duration::from_millis(10)) - .expect("failed to read_timeout"), - EventWaitResult::Signaled - ); - } - - #[test] - fn apic_mmio() { - let chip = get_chip(2); - let mut data = [0u8; 4]; - chip.read( - BusAccessInfo { - id: 0, - address: APIC_BASE_ADDRESS + APIC_ID, - offset: APIC_ID, - }, - &mut data, - ); - assert_eq!(data, [0, 0, 0, 0]); - chip.read( - BusAccessInfo { - id: 1, - address: APIC_BASE_ADDRESS + APIC_ID, - offset: APIC_ID, - }, - &mut data, - ); - assert_eq!(data, [0, 0, 0, 1]); - } - - // TODO(b/237977699): remove reliance on sleep - // #[test] - fn runnable_vcpu_unhalts() { - let chip = get_chip(1); - let vcpu = get_vcpus(&chip).remove(0); - let chip_copy = chip.try_clone().unwrap(); - // BSP starts runnable. - assert_eq!(chip.wait_until_runnable(&vcpu), Ok(VcpuRunState::Runnable)); - let start = Instant::now(); - let handle = thread::spawn(move || { - thread::sleep(TEST_SLEEP_DURATION); - chip_copy.send_irq_to_apic( - 0, - &InterruptData { - vector: 123, - delivery: DeliveryMode::Fixed, - trigger: TriggerMode::Level, - level: Level::Assert, - }, - ); - }); - chip.halted(0); - assert_eq!(chip.wait_until_runnable(&vcpu), Ok(VcpuRunState::Runnable)); - assert!(Instant::now() - start > Duration::from_millis(5)); - handle.join().unwrap(); - } - - // TODO(b/237977699): remove reliance on sleep - // #[test] - fn kicked_vcpu_unhalts() { - let chip = get_chip(1); - let vcpu = get_vcpus(&chip).remove(0); - let chip_copy = chip.try_clone().unwrap(); - // BSP starts runnable. - assert_eq!(chip.wait_until_runnable(&vcpu), Ok(VcpuRunState::Runnable)); - let start = Instant::now(); - let handle = thread::spawn(move || { - thread::sleep(TEST_SLEEP_DURATION); - chip_copy.kick_halted_vcpus(); - }); - chip.halted(0); - assert_eq!( - chip.wait_until_runnable(&vcpu), - Ok(VcpuRunState::Interrupted) - ); - assert!(Instant::now() - start > Duration::from_millis(5)); - handle.join().unwrap(); - } - - // TODO(b/237977699): remove reliance on sleep - // #[test] - fn apic_timer() { - let clock = Arc::new(Mutex::new(Clock::new())); - let mut chip = get_chip_with_clock(2, clock.clone()); - let vcpus = get_vcpus(&chip); - let cycle_length = chip.apics[0].lock().get_cycle_length(); - assert_eq!(chip.apics[1].lock().get_cycle_length(), cycle_length); - - // finalize_devices() starts the timer worker threads. - let mmio_bus = Bus::new(); - let io_bus = Bus::new(); - let mut resources = SystemAllocator::new( - SystemAllocatorConfig { - io: Some(AddressRange { - start: 0xc000, - end: 0xFFFF, - }), - low_mmio: AddressRange { - start: 0, - end: 2047, - }, - high_mmio: AddressRange { - start: 2048, - end: 6143, - }, - platform_mmio: None, - first_irq: 5, - }, - None, - &[], - ) - .expect("failed to create SystemAllocator"); - chip.finalize_devices(&mut resources, &io_bus, &mmio_bus) - .expect("failed to finalize devices"); - - let val = TIMER_MODE_PERIODIC | 111; - chip.write( - BusAccessInfo { - id: 0, - address: APIC_BASE_ADDRESS + LOCAL_TIMER, - offset: LOCAL_TIMER, - }, - &val.to_le_bytes(), - ); - chip.write( - BusAccessInfo { - id: 0, - address: APIC_BASE_ADDRESS + TIMER_DIVIDE_CONTROL, - offset: TIMER_DIVIDE_CONTROL, - }, - &[TIMER_FREQ_DIVIDE_BY_1, 0, 0, 0], - ); - chip.write( - BusAccessInfo { - id: 0, - address: APIC_BASE_ADDRESS + TIMER_INITIAL_COUNT, - offset: TIMER_INITIAL_COUNT, - }, - &10u32.to_le_bytes(), - ); - let val = TIMER_MODE_PERIODIC | 222; - chip.write( - BusAccessInfo { - id: 1, - address: APIC_BASE_ADDRESS + LOCAL_TIMER, - offset: LOCAL_TIMER, - }, - &val.to_le_bytes(), - ); - chip.write( - BusAccessInfo { - id: 1, - address: APIC_BASE_ADDRESS + TIMER_DIVIDE_CONTROL, - offset: TIMER_DIVIDE_CONTROL, - }, - &[TIMER_FREQ_DIVIDE_BY_1, 0, 0, 0], - ); - chip.write( - BusAccessInfo { - id: 1, - address: APIC_BASE_ADDRESS + TIMER_INITIAL_COUNT, - offset: TIMER_INITIAL_COUNT, - }, - &20u32.to_le_bytes(), - ); - - // Neither timer should fire after 9ns. - clock.lock().add_ns(9 * cycle_length.as_nanos() as u64); - thread::sleep(TEST_SLEEP_DURATION); - chip.inject_interrupts(&vcpus[0]).unwrap(); - chip.inject_interrupts(&vcpus[1]).unwrap(); - assert_eq!(vcpus[0].clear_injected(), None); - assert_eq!(vcpus[1].clear_injected(), None); - - // Apic 0 should fire after 10ns. - clock.lock().add_ns(1 * cycle_length.as_nanos() as u64); - thread::sleep(TEST_SLEEP_DURATION); - chip.inject_interrupts(&vcpus[0]).unwrap(); - chip.inject_interrupts(&vcpus[1]).unwrap(); - assert_eq!(vcpus[0].clear_injected(), Some(111)); - assert_eq!(vcpus[1].clear_injected(), None); - chip.write( - BusAccessInfo { - id: 0, - address: APIC_BASE_ADDRESS + EOI, - offset: EOI, - }, - &[0; 4], - ); - - clock.lock().add_ns(9 * cycle_length.as_nanos() as u64); - thread::sleep(TEST_SLEEP_DURATION); - chip.inject_interrupts(&vcpus[0]).unwrap(); - chip.inject_interrupts(&vcpus[1]).unwrap(); - assert_eq!(vcpus[0].clear_injected(), None); - assert_eq!(vcpus[1].clear_injected(), None); - - // Apic 1 should fire after 20ns, and apic 0 should fire again. - clock.lock().add_ns(1 * cycle_length.as_nanos() as u64); - thread::sleep(TEST_SLEEP_DURATION); - chip.inject_interrupts(&vcpus[0]).unwrap(); - chip.inject_interrupts(&vcpus[1]).unwrap(); - assert_eq!(vcpus[0].clear_injected(), Some(111)); - assert_eq!(vcpus[1].clear_injected(), Some(222)); - } - - /// Mock vcpu for testing interrupt injection. - struct FakeVcpu { - id: usize, - requested: Arc>, - ready: Arc>, - injected: Arc>>, - } - - impl FakeVcpu { - /// Returns and clears the last interrupt set by `interrupt`. - fn clear_injected(&self) -> Option { - self.injected.lock().take() - } - - /// Returns true if an interrupt window was requested with `set_interrupt_window_requested`. - fn window_requested(&self) -> bool { - *self.requested.lock() - } - - /// Sets the value to be returned by `ready_for_interrupt`. - fn set_ready(&self, val: bool) { - *self.ready.lock() = val; - } - } - - impl Vcpu for FakeVcpu { - fn try_clone(&self) -> Result { - Ok(FakeVcpu { - id: self.id, - requested: self.requested.clone(), - ready: self.ready.clone(), - injected: self.injected.clone(), - }) - } - - fn id(&self) -> usize { - self.id - } - - fn as_vcpu(&self) -> &dyn Vcpu { - self - } - - fn take_run_handle(&self, _signal_num: Option) -> Result { - unimplemented!() - } - fn run(&mut self, _run_handle: &VcpuRunHandle) -> Result { - unimplemented!() - } - fn set_immediate_exit(&self, _exit: bool) {} - fn set_local_immediate_exit(_exit: bool) {} - fn set_local_immediate_exit_fn(&self) -> extern "C" fn() { - extern "C" fn f() { - FakeVcpu::set_local_immediate_exit(true); - } - f - } - fn handle_mmio( - &self, - _handle_fn: &mut dyn FnMut(IoParams) -> Option<[u8; 8]>, - ) -> Result<()> { - unimplemented!() - } - fn handle_io(&self, _handle_fn: &mut dyn FnMut(IoParams) -> Option<[u8; 8]>) -> Result<()> { - unimplemented!() - } - fn handle_hyperv_hypercall( - &self, - _func: &mut dyn FnMut(HypervHypercall) -> u64, - ) -> Result<()> { - unimplemented!() - } - fn handle_rdmsr(&self, _data: u64) -> Result<()> { - unimplemented!() - } - fn handle_wrmsr(&self) { - unimplemented!() - } - fn pvclock_ctrl(&self) -> Result<()> { - unimplemented!() - } - fn set_signal_mask(&self, _signals: &[c_int]) -> Result<()> { - unimplemented!() - } - unsafe fn enable_raw_capability(&self, _cap: u32, _args: &[u64; 4]) -> Result<()> { - unimplemented!() - } - } - - impl VcpuX86_64 for FakeVcpu { - fn set_interrupt_window_requested(&self, requested: bool) { - *self.requested.lock() = requested; - } - - fn ready_for_interrupt(&self) -> bool { - *self.ready.lock() - } - - fn interrupt(&self, irq: u32) -> Result<()> { - *self.injected.lock() = Some(irq); - Ok(()) - } - - fn inject_nmi(&self) -> Result<()> { - Ok(()) - } - - fn get_regs(&self) -> Result { - unimplemented!() - } - fn set_regs(&self, _regs: &Regs) -> Result<()> { - unimplemented!() - } - fn get_sregs(&self) -> Result { - unimplemented!() - } - fn set_sregs(&self, _sregs: &Sregs) -> Result<()> { - unimplemented!() - } - fn get_fpu(&self) -> Result { - unimplemented!() - } - fn set_fpu(&self, _fpu: &Fpu) -> Result<()> { - unimplemented!() - } - fn get_debugregs(&self) -> Result { - unimplemented!() - } - fn set_debugregs(&self, _debugregs: &DebugRegs) -> Result<()> { - unimplemented!() - } - fn get_xcrs(&self) -> Result> { - unimplemented!() - } - fn set_xcrs(&self, _xcrs: &[Register]) -> Result<()> { - unimplemented!() - } - fn get_msrs(&self, _msrs: &mut Vec) -> Result<()> { - unimplemented!() - } - fn set_msrs(&self, _msrs: &[Register]) -> Result<()> { - unimplemented!() - } - fn set_cpuid(&self, _cpuid: &CpuId) -> Result<()> { - unimplemented!() - } - fn handle_cpuid(&mut self, _entry: &CpuIdEntry) -> Result<()> { - unimplemented!() - } - fn get_hyperv_cpuid(&self) -> Result { - unimplemented!() - } - fn set_guest_debug(&self, _addrs: &[GuestAddress], _enable_singlestep: bool) -> Result<()> { - unimplemented!() - } - fn get_tsc_offset(&self) -> Result { - unimplemented!() - } - fn set_tsc_offset(&self, _offset: u64) -> Result<()> { - unimplemented!() - } - } -} diff --git a/devices/src/irqchip/x86_64.rs b/devices/src/irqchip/x86_64.rs index 3b7377ccad..0155f4a9b3 100644 --- a/devices/src/irqchip/x86_64.rs +++ b/devices/src/irqchip/x86_64.rs @@ -61,7 +61,7 @@ pub trait IrqChipX86_64: IrqChip { } /// A container for x86 IrqRoutes, grouped by GSI. -pub(super) struct Routes { +pub struct Routes { /// A list of routes, indexed by GSI. Each GSI can map to zero or more routes, so this is a /// Vec of Vecs. Specifically, a GSI can map to: /// * no routes; or @@ -200,285 +200,3 @@ impl DelayedIoApicIrqEvents { }) } } - -#[cfg_attr(windows, allow(dead_code))] -#[cfg(test)] -/// This module contains tests that apply to any implementations of IrqChipX86_64 -pub(super) mod tests { - use hypervisor::IrqRoute; - use hypervisor::IrqSource; - use hypervisor::IrqSourceChip; - - use super::*; - - pub fn test_get_pic(mut chip: impl IrqChipX86_64) { - 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 - chip.service_irq(0, true).expect("could not service irq"); - - 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); - } - - pub fn test_set_pic(mut chip: impl IrqChipX86_64) { - 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); - } - - pub fn test_get_ioapic(mut chip: impl IrqChipX86_64) { - 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 - chip.service_irq(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); - } - - pub fn test_set_ioapic(mut chip: impl IrqChipX86_64) { - 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); - } - - pub fn test_get_pit(chip: impl IrqChipX86_64) { - 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); - } - } - - pub fn test_set_pit(mut chip: impl IrqChipX86_64) { - 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); - } - - pub fn test_get_lapic(chip: impl IrqChipX86_64) { - let state = chip.get_lapic_state(0).expect("failed to get lapic state"); - - // Checking some APIC reg defaults for KVM: - // DFR default is 0xffffffff - assert_eq!(state.regs[0xe], 0xffffffff); - // SPIV default is 0xff - assert_eq!(state.regs[0xf], 0xff); - } - - pub fn test_set_lapic(mut chip: impl IrqChipX86_64) { - // Get default state - let mut state = chip.get_lapic_state(0).expect("failed to get lapic state"); - - // ESR should start out as 0 - assert_eq!(state.regs[8], 0); - // Set a value in the ESR - state.regs[8] = 1 << 8; - chip.set_lapic_state(0, &state) - .expect("failed to set lapic state"); - - // check that new ESR value stuck - let state = chip.get_lapic_state(0).expect("failed to get lapic state"); - assert_eq!(state.regs[8], 1 << 8); - } - - /// Helper function for checking the pic interrupt status - fn check_pic_interrupts(chip: &impl IrqChipX86_64, select: PicSelect, value: u8) { - let state = chip - .get_pic_state(select) - .expect("could not get ioapic state"); - - assert_eq!(state.irr, value); - } - - /// Helper function for checking the ioapic interrupt status - fn check_ioapic_interrupts(chip: &impl IrqChipX86_64, value: u32) { - let state = chip.get_ioapic_state().expect("could not get ioapic state"); - - // since the irq route goes nowhere the bitmap should still be 0 - assert_eq!(state.current_interrupt_level_bitmap, value); - } - - pub fn test_route_irq(mut chip: impl IrqChipX86_64) { - // clear out irq routes - chip.set_irq_routes(&[]) - .expect("failed to set empty irq routes"); - // assert Irq Line 1 - chip.service_irq(1, true).expect("could not set irq line"); - - // no pic or ioapic interrupts should be asserted - check_pic_interrupts(&chip, PicSelect::Primary, 0); - check_ioapic_interrupts(&chip, 0); - - // now we route gsi 1 to pin 3 of the ioapic and pin 6 of the primary pic - chip.route_irq(IrqRoute { - gsi: 1, - source: IrqSource::Irqchip { - chip: IrqSourceChip::Ioapic, - pin: 3, - }, - }) - .expect("failed to assert irq route"); - // re-assert Irq Line 1 - chip.service_irq(1, true).expect("could not set irq line"); - - // no pic line should be asserted, ioapic pin 3 should be asserted - check_pic_interrupts(&chip, PicSelect::Primary, 0); - check_ioapic_interrupts(&chip, 1 << 3); - - // de-assert Irq Line 1 - chip.service_irq(1, false).expect("could not set irq line"); - - // no pic or ioapic interrupts should be asserted - check_pic_interrupts(&chip, PicSelect::Primary, 0); - check_ioapic_interrupts(&chip, 0); - - // add pic route - chip.route_irq(IrqRoute { - gsi: 2, - source: IrqSource::Irqchip { - chip: IrqSourceChip::PicPrimary, - pin: 6, - }, - }) - .expect("failed to route irq"); - - // re-assert Irq Line 1, it should still affect only the ioapic - chip.service_irq(1, true).expect("could not set irq line"); - - // no pic line should be asserted, ioapic pin 3 should be asserted - check_pic_interrupts(&chip, PicSelect::Primary, 0); - check_ioapic_interrupts(&chip, 1 << 3); - - // assert Irq Line 2 - chip.service_irq(2, true).expect("could not set irq line"); - - // pic pin 6 should be asserted, ioapic pin 3 should be asserted - check_pic_interrupts(&chip, PicSelect::Primary, 1 << 6); - check_ioapic_interrupts(&chip, 1 << 3); - } - - #[test] - fn add_routes() { - let ioapic_pins = hypervisor::NUM_IOAPIC_PINS; - let mut r = Routes::new(); - r.replace_all(&Routes::default_pic_ioapic_routes(ioapic_pins)) - .unwrap(); - - assert_eq!(r[0].len(), 2); - - assert_eq!(r[ioapic_pins - 1].len(), 1); - r.add(IrqRoute { - gsi: ioapic_pins as u32 - 1, - source: IrqSource::Irqchip { - chip: IrqSourceChip::Ioapic, - pin: 3, - }, - }) - .unwrap(); - assert_eq!(r[ioapic_pins - 1].len(), 1); - r.add(IrqRoute { - gsi: ioapic_pins as u32 - 1, - source: IrqSource::Irqchip { - chip: IrqSourceChip::PicPrimary, - pin: 3, - }, - }) - .unwrap(); - assert_eq!(r[ioapic_pins - 1].len(), 2); - assert!(r - .add(IrqRoute { - gsi: ioapic_pins as u32 - 1, - source: IrqSource::Msi { - address: 0, - data: 0 - }, - }) - .is_err(),); - assert_eq!(r[ioapic_pins - 1].len(), 2); - - assert_eq!(r[ioapic_pins].len(), 0); - r.add(IrqRoute { - gsi: ioapic_pins as u32, - source: IrqSource::Msi { - address: 0, - data: 0, - }, - }) - .unwrap(); - assert_eq!(r[ioapic_pins].len(), 1); - assert!(r - .add(IrqRoute { - gsi: ioapic_pins as u32, - source: IrqSource::Irqchip { - chip: IrqSourceChip::Ioapic, - pin: 3 - }, - }) - .is_err(),); - assert_eq!(r[ioapic_pins].len(), 1); - - assert_eq!(r[500].len(), 0); - } -} diff --git a/devices/src/pci/ac97_bus_master/sys/unix.rs b/devices/src/pci/ac97_bus_master/sys/unix.rs index 14a6f456db..883d6ff599 100644 --- a/devices/src/pci/ac97_bus_master/sys/unix.rs +++ b/devices/src/pci/ac97_bus_master/sys/unix.rs @@ -437,7 +437,7 @@ mod tests { const FRAGMENT_SIZE: usize = BUFFER_SIZE / 2; const GUEST_ADDR_BASE: u32 = 0x100_0000; - let mem = GuestMemory::new(&[(GuestAddress(GUEST_ADDR_BASE as u64), 1024 * 1024 * 1024)]) + let mem = GuestMemory::new(&[(GuestAddress(GUEST_ADDR_BASE as u64), 1024 * 1024 * 8)]) .expect("Creating guest memory failed."); let stream_source = MockShmStreamSource::new(); let mut bm = Ac97BusMaster::new(mem.clone(), Box::new(stream_source.clone())); @@ -545,7 +545,7 @@ mod tests { const FRAGMENT_SIZE: usize = BUFFER_SIZE / 2; const GUEST_ADDR_BASE: u32 = 0x100_0000; - let mem = GuestMemory::new(&[(GuestAddress(GUEST_ADDR_BASE as u64), 1024 * 1024 * 1024)]) + let mem = GuestMemory::new(&[(GuestAddress(GUEST_ADDR_BASE as u64), 1024 * 1024 * 8)]) .expect("Creating guest memory failed."); let stream_source = MockShmStreamSource::new(); let mut bm = Ac97BusMaster::new(mem.clone(), Box::new(stream_source.clone())); diff --git a/devices/tests/irqchip/kvm/mod.rs b/devices/tests/irqchip/kvm/mod.rs new file mode 100644 index 0000000000..c9f6b3450f --- /dev/null +++ b/devices/tests/irqchip/kvm/mod.rs @@ -0,0 +1,57 @@ +// Copyright 2022 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#![cfg(unix)] + +mod x86_64; + +use hypervisor::kvm::Kvm; +use hypervisor::kvm::KvmVm; +use hypervisor::MPState; +use hypervisor::Vm; +#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] +use hypervisor::VmAArch64; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use hypervisor::VmX86_64; +use vm_memory::GuestMemory; + +use devices::irqchip::IrqChip; +use devices::irqchip::KvmKernelIrqChip; + +#[test] +fn create_kvm_kernel_irqchip() { + let kvm = Kvm::new().expect("failed to instantiate Kvm"); + let mem = GuestMemory::new(&[]).unwrap(); + let vm = KvmVm::new(&kvm, mem, Default::default()).expect("failed to instantiate vm"); + + let mut chip = KvmKernelIrqChip::new(vm.try_clone().expect("failed to clone vm"), 1) + .expect("failed to instantiate KvmKernelIrqChip"); + + let vcpu = vm.create_vcpu(0).expect("failed to instantiate vcpu"); + chip.add_vcpu(0, vcpu.as_vcpu()) + .expect("failed to add vcpu"); +} + +#[test] +fn mp_state() { + let kvm = Kvm::new().expect("failed to instantiate Kvm"); + let mem = GuestMemory::new(&[]).unwrap(); + let vm = KvmVm::new(&kvm, mem, Default::default()).expect("failed to instantiate vm"); + + let mut chip = KvmKernelIrqChip::new(vm.try_clone().expect("failed to clone vm"), 1) + .expect("failed to instantiate KvmKernelIrqChip"); + + let vcpu = vm.create_vcpu(0).expect("failed to instantiate vcpu"); + chip.add_vcpu(0, vcpu.as_vcpu()) + .expect("failed to add vcpu"); + + let state = chip.get_mp_state(0).expect("failed to get mp state"); + assert_eq!(state, MPState::Runnable); + + chip.set_mp_state(0, &MPState::Stopped) + .expect("failed to set mp state"); + + let state = chip.get_mp_state(0).expect("failed to get mp state"); + assert_eq!(state, MPState::Stopped); +} diff --git a/devices/tests/irqchip/kvm/x86_64.rs b/devices/tests/irqchip/kvm/x86_64.rs new file mode 100644 index 0000000000..f0e7d6cd4c --- /dev/null +++ b/devices/tests/irqchip/kvm/x86_64.rs @@ -0,0 +1,450 @@ +// Copyright 2022 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#![cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use base::EventWaitResult; +use base::Tube; +use hypervisor::kvm::Kvm; +use hypervisor::kvm::KvmVm; +use hypervisor::IoapicRedirectionTableEntry; +use hypervisor::IrqRoute; +use hypervisor::IrqSource; +use hypervisor::PicSelect; +use hypervisor::PitRWMode; +use hypervisor::TriggerMode; +use hypervisor::Vm; +use hypervisor::VmX86_64; +use resources::AddressRange; +use resources::SystemAllocator; +use resources::SystemAllocatorConfig; +use vm_memory::GuestMemory; + +use crate::x86_64::{ + test_get_ioapic, test_get_lapic, test_get_pic, test_get_pit, test_route_irq, test_set_ioapic, + test_set_lapic, test_set_pic, test_set_pit, +}; +use devices::CrosvmDeviceId; +use devices::DeviceId; +use devices::IrqChip; +use devices::{ + Bus, IrqChipX86_64, IrqEdgeEvent, IrqEventSource, IrqLevelEvent, KvmKernelIrqChip, + KvmSplitIrqChip, IOAPIC_BASE_ADDRESS, +}; + +/// Helper function for setting up a KvmKernelIrqChip +fn get_kernel_chip() -> KvmKernelIrqChip { + let kvm = Kvm::new().expect("failed to instantiate Kvm"); + let mem = GuestMemory::new(&[]).unwrap(); + let vm = KvmVm::new(&kvm, mem, Default::default()).expect("failed tso instantiate vm"); + + let mut chip = KvmKernelIrqChip::new(vm.try_clone().expect("failed to clone vm"), 1) + .expect("failed to instantiate KvmKernelIrqChip"); + + let vcpu = vm.create_vcpu(0).expect("failed to instantiate vcpu"); + chip.add_vcpu(0, vcpu.as_vcpu()) + .expect("failed to add vcpu"); + + chip +} + +/// Helper function for setting up a KvmSplitIrqChip +fn get_split_chip() -> KvmSplitIrqChip { + let kvm = Kvm::new().expect("failed to instantiate Kvm"); + let mem = GuestMemory::new(&[]).unwrap(); + let vm = KvmVm::new(&kvm, mem, Default::default()).expect("failed tso instantiate vm"); + + let (_, device_tube) = Tube::pair().expect("failed to create irq tube"); + + let mut chip = KvmSplitIrqChip::new( + vm.try_clone().expect("failed to clone vm"), + 1, + device_tube, + None, + ) + .expect("failed to instantiate KvmKernelIrqChip"); + + let vcpu = vm.create_vcpu(0).expect("failed to instantiate vcpu"); + chip.add_vcpu(0, vcpu.as_vcpu()) + .expect("failed to add vcpu"); + chip +} + +#[test] +fn kernel_irqchip_pit_uses_speaker_port() { + let chip = get_kernel_chip(); + assert!(!chip.pit_uses_speaker_port()); +} + +#[test] +fn kernel_irqchip_get_pic() { + test_get_pic(get_kernel_chip()); +} + +#[test] +fn kernel_irqchip_set_pic() { + test_set_pic(get_kernel_chip()); +} + +#[test] +fn kernel_irqchip_get_ioapic() { + test_get_ioapic(get_kernel_chip()); +} + +#[test] +fn kernel_irqchip_set_ioapic() { + test_set_ioapic(get_kernel_chip()); +} + +#[test] +fn kernel_irqchip_get_pit() { + test_get_pit(get_kernel_chip()); +} + +#[test] +fn kernel_irqchip_set_pit() { + test_set_pit(get_kernel_chip()); +} + +#[test] +fn kernel_irqchip_get_lapic() { + test_get_lapic(get_kernel_chip()) +} + +#[test] +fn kernel_irqchip_set_lapic() { + test_set_lapic(get_kernel_chip()) +} + +#[test] +fn kernel_irqchip_route_irq() { + test_route_irq(get_kernel_chip()); +} + +#[test] +fn split_irqchip_get_pic() { + test_get_pic(get_split_chip()); +} + +#[test] +fn split_irqchip_set_pic() { + test_set_pic(get_split_chip()); +} + +#[test] +fn split_irqchip_get_ioapic() { + test_get_ioapic(get_split_chip()); +} + +#[test] +fn split_irqchip_set_ioapic() { + test_set_ioapic(get_split_chip()); +} + +#[test] +fn split_irqchip_get_pit() { + test_get_pit(get_split_chip()); +} + +#[test] +fn split_irqchip_set_pit() { + test_set_pit(get_split_chip()); +} + +#[test] +fn split_irqchip_route_irq() { + test_route_irq(get_split_chip()); +} + +#[test] +fn split_irqchip_pit_uses_speaker_port() { + let chip = get_split_chip(); + assert!(chip.pit_uses_speaker_port()); +} + +#[test] +fn split_irqchip_routes_conflict() { + let mut chip = get_split_chip(); + chip.route_irq(IrqRoute { + gsi: 5, + source: IrqSource::Msi { + address: 4276092928, + data: 0, + }, + }) + .expect("failed to set msi rout"); + // this second route should replace the first + chip.route_irq(IrqRoute { + gsi: 5, + source: IrqSource::Msi { + address: 4276092928, + data: 32801, + }, + }) + .expect("failed to set msi rout"); +} + +#[test] +fn irq_event_tokens() { + let mut chip = get_split_chip(); + let tokens = chip + .irq_event_tokens() + .expect("could not get irq_event_tokens"); + + // there should be one token on a fresh split irqchip, for the pit + assert_eq!(tokens.len(), 1); + assert_eq!(tokens[0].1.device_name, "userspace PIT"); + + // register another irq event + let evt = IrqEdgeEvent::new().expect("failed to create event"); + let source = IrqEventSource { + device_id: CrosvmDeviceId::Cmos.into(), + queue_id: 0, + device_name: "test".into(), + }; + chip.register_edge_irq_event(6, &evt, source) + .expect("failed to register irq event"); + + let tokens = chip + .irq_event_tokens() + .expect("could not get irq_event_tokens"); + + // now there should be two tokens + assert_eq!(tokens.len(), 2); + assert_eq!(tokens[0].1.device_name, "userspace PIT"); + assert_eq!( + tokens[1].1.device_id, + DeviceId::PlatformDeviceId(CrosvmDeviceId::Cmos) + ); + assert_eq!(tokens[1].2, *evt.get_trigger()); +} + +#[test] +fn finalize_devices() { + let mut chip = get_split_chip(); + + let mmio_bus = Bus::new(); + let io_bus = Bus::new(); + let mut resources = SystemAllocator::new( + SystemAllocatorConfig { + io: Some(AddressRange { + start: 0xc000, + end: 0xffff, + }), + low_mmio: AddressRange { + start: 0, + end: 2047, + }, + high_mmio: AddressRange { + start: 0x1_0000_0000, + end: 0x2_ffff_ffff, + }, + platform_mmio: None, + first_irq: 5, + }, + None, + &[], + ) + .expect("failed to create SystemAllocator"); + + // Set up a level-triggered interrupt line 1 + let evt = IrqLevelEvent::new().expect("failed to create event"); + + let source = IrqEventSource { + device_id: CrosvmDeviceId::Cmos.into(), + device_name: "test".into(), + queue_id: 0, + }; + let evt_index = chip + .register_level_irq_event(1, &evt, source) + .expect("failed to register irq event") + .expect("register_irq_event should not return None"); + + // Once we finalize devices, the pic/pit/ioapic should be attached to io and mmio busses + chip.finalize_devices(&mut resources, &io_bus, &mmio_bus) + .expect("failed to finalize devices"); + + // Should not be able to allocate an irq < 24 now + assert!(resources.allocate_irq().expect("failed to allocate irq") >= 24); + + // set PIT counter 2 to "SquareWaveGen"(aka 3) mode and "Both" access mode + io_bus.write(0x43, &[0b10110110]); + + let state = chip.get_pit().expect("failed to get pit state"); + assert_eq!(state.channels[2].mode, 3); + assert_eq!(state.channels[2].rw_mode, PitRWMode::Both); + + // ICW1 0x11: Edge trigger, cascade mode, ICW4 needed. + // ICW2 0x08: Interrupt vector base address 0x08. + // ICW3 0xff: Value written does not matter. + // ICW4 0x13: Special fully nested mode, auto EOI. + io_bus.write(0x20, &[0x11]); + io_bus.write(0x21, &[0x08]); + io_bus.write(0x21, &[0xff]); + io_bus.write(0x21, &[0x13]); + + let state = chip + .get_pic_state(PicSelect::Primary) + .expect("failed to get pic state"); + + // auto eoi and special fully nested mode should be turned on + assert!(state.auto_eoi); + assert!(state.special_fully_nested_mode); + + // Need to write to the irq event before servicing it + evt.trigger().expect("failed to write to event"); + + // if we assert irq line one, and then get the resulting interrupt, an auto-eoi should + // occur and cause the resample_event to be written to + chip.service_irq_event(evt_index) + .expect("failed to service irq"); + + assert!(chip.interrupt_requested(0)); + assert_eq!( + chip.get_external_interrupt(0) + .expect("failed to get external interrupt"), + // Vector is 9 because the interrupt vector base address is 0x08 and this is irq + // line 1 and 8+1 = 9 + 0x9 + ); + + // Clone resample event because read_timeout() needs a mutable reference. + let resample_evt = evt.get_resample().try_clone().unwrap(); + assert_eq!( + resample_evt + .wait_timeout(std::time::Duration::from_secs(1)) + .expect("failed to read_timeout"), + EventWaitResult::Signaled + ); + + // setup a ioapic redirection table entry 14 + let mut entry = IoapicRedirectionTableEntry::default(); + entry.set_vector(44); + + let irq_14_offset = 0x10 + 14 * 2; + mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_14_offset]); + mmio_bus.write( + IOAPIC_BASE_ADDRESS + 0x10, + &(entry.get(0, 32) as u32).to_ne_bytes(), + ); + mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_14_offset + 1]); + mmio_bus.write( + IOAPIC_BASE_ADDRESS + 0x10, + &(entry.get(32, 32) as u32).to_ne_bytes(), + ); + + let state = chip.get_ioapic_state().expect("failed to get ioapic state"); + + // redirection table entry 14 should have a vector of 44 + assert_eq!(state.redirect_table[14].get_vector(), 44); +} + +#[test] +fn get_external_interrupt() { + let mut chip = get_split_chip(); + assert!(!chip.interrupt_requested(0)); + + chip.service_irq(0, true).expect("failed to service irq"); + assert!(chip.interrupt_requested(0)); + + // Should return Some interrupt + assert_eq!( + chip.get_external_interrupt(0) + .expect("failed to get external interrupt"), + 0, + ); + + // interrupt is not requested twice + assert!(!chip.interrupt_requested(0)); +} + +#[test] +fn broadcast_eoi() { + let mut chip = get_split_chip(); + + let mmio_bus = Bus::new(); + let io_bus = Bus::new(); + let mut resources = SystemAllocator::new( + SystemAllocatorConfig { + io: Some(AddressRange { + start: 0xc000, + end: 0xffff, + }), + low_mmio: AddressRange { + start: 0, + end: 2047, + }, + high_mmio: AddressRange { + start: 0x1_0000_0000, + end: 0x2_ffff_ffff, + }, + platform_mmio: None, + first_irq: 5, + }, + None, + &[], + ) + .expect("failed to create SystemAllocator"); + + let source = IrqEventSource { + device_id: CrosvmDeviceId::Cmos.into(), + device_name: "test".into(), + queue_id: 0, + }; + // setup an event and a resample event for irq line 1 + let evt = IrqLevelEvent::new().expect("failed to create event"); + + chip.register_level_irq_event(1, &evt, source) + .expect("failed to register_level_irq_event"); + + // Once we finalize devices, the pic/pit/ioapic should be attached to io and mmio busses + chip.finalize_devices(&mut resources, &io_bus, &mmio_bus) + .expect("failed to finalize devices"); + + // setup a ioapic redirection table entry 1 with a vector of 123 + let mut entry = IoapicRedirectionTableEntry::default(); + entry.set_vector(123); + entry.set_trigger_mode(TriggerMode::Level); + + let irq_write_offset = 0x10 + 1 * 2; + mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_write_offset]); + mmio_bus.write( + IOAPIC_BASE_ADDRESS + 0x10, + &(entry.get(0, 32) as u32).to_ne_bytes(), + ); + mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_write_offset + 1]); + mmio_bus.write( + IOAPIC_BASE_ADDRESS + 0x10, + &(entry.get(32, 32) as u32).to_ne_bytes(), + ); + + // Assert line 1 + chip.service_irq(1, true).expect("failed to service irq"); + + // resample event should not be written to + assert_eq!( + evt.get_resample() + .wait_timeout(std::time::Duration::from_millis(10)) + .expect("failed to read_timeout"), + EventWaitResult::TimedOut + ); + + // irq line 1 should be asserted + let state = chip.get_ioapic_state().expect("failed to get ioapic state"); + assert_eq!(state.current_interrupt_level_bitmap, 1 << 1); + + // Now broadcast an eoi for vector 123 + chip.broadcast_eoi(123).expect("failed to broadcast eoi"); + + // irq line 1 should be deasserted + let state = chip.get_ioapic_state().expect("failed to get ioapic state"); + assert_eq!(state.current_interrupt_level_bitmap, 0); + + // resample event should be written to by ioapic + assert_eq!( + evt.get_resample() + .wait_timeout(std::time::Duration::from_millis(10)) + .expect("failed to read_timeout"), + EventWaitResult::Signaled + ); +} diff --git a/devices/tests/irqchip/main.rs b/devices/tests/irqchip/main.rs new file mode 100644 index 0000000000..67da9a3734 --- /dev/null +++ b/devices/tests/irqchip/main.rs @@ -0,0 +1,7 @@ +// Copyright 2022 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +mod kvm; +mod userspace; +mod x86_64; diff --git a/devices/tests/irqchip/userspace.rs b/devices/tests/irqchip/userspace.rs new file mode 100644 index 0000000000..ef38601906 --- /dev/null +++ b/devices/tests/irqchip/userspace.rs @@ -0,0 +1,779 @@ +// Copyright 2022 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#![cfg(any(target_arch = "x86", target_arch = "x86_64"))] + +use std::os::raw::c_int; +use std::sync::Arc; +use std::thread; +use std::time::Duration; +use std::time::Instant; + +use base::Clock; +use base::EventWaitResult; + +use base::Result; +use base::Tube; +use devices::Bus; +use devices::BusAccessInfo; +use devices::BusDeviceSync; +use devices::CrosvmDeviceId; +use devices::DestinationShorthand; +use devices::DeviceId; +use devices::Interrupt; +use devices::InterruptData; +use devices::InterruptDestination; +use devices::IrqChip; +use devices::IrqChipX86_64; +use devices::IrqEdgeEvent; +use devices::IrqEventSource; +use devices::IrqLevelEvent; +use devices::UserspaceIrqChip; +use devices::VcpuRunState; +use devices::APIC_BASE_ADDRESS; +use devices::IOAPIC_BASE_ADDRESS; +use hypervisor::CpuId; +use hypervisor::CpuIdEntry; +use hypervisor::DebugRegs; +use hypervisor::DeliveryMode; +use hypervisor::DestinationMode; +use hypervisor::Fpu; +use hypervisor::HypervHypercall; +use hypervisor::IoParams; +use hypervisor::IoapicRedirectionTableEntry; +use hypervisor::IrqRoute; +use hypervisor::IrqSource; +use hypervisor::Level; +use hypervisor::PicSelect; +use hypervisor::PitRWMode; +use hypervisor::Register; +use hypervisor::Regs; +use hypervisor::Sregs; +use hypervisor::TriggerMode; +use hypervisor::Vcpu; +use hypervisor::VcpuExit; +use hypervisor::VcpuRunHandle; +use hypervisor::VcpuX86_64; +use resources::AddressRange; +use resources::SystemAllocator; +use resources::SystemAllocatorConfig; +use sync::Mutex; +use vm_memory::GuestAddress; + +use crate::x86_64::test_get_ioapic; +use crate::x86_64::test_get_pit; +use crate::x86_64::test_route_irq; +use crate::x86_64::test_set_ioapic; +use crate::x86_64::test_set_pic; +use crate::x86_64::test_set_pit; + +const APIC_ID: u64 = 0x20; +const TPR: u64 = 0x80; +const EOI: u64 = 0xB0; +const TEST_SLEEP_DURATION: Duration = Duration::from_millis(50); + +/// Helper function for setting up a UserspaceIrqChip. +fn get_chip(num_vcpus: usize) -> UserspaceIrqChip { + get_chip_with_clock(num_vcpus, Arc::new(Mutex::new(Clock::new()))) +} + +fn get_chip_with_clock(num_vcpus: usize, clock: Arc>) -> UserspaceIrqChip { + let (_, irq_tube) = Tube::pair().unwrap(); + let mut chip = UserspaceIrqChip::::new_with_clock(num_vcpus, irq_tube, None, clock) + .expect("failed to instantiate UserspaceIrqChip"); + + for i in 0..num_vcpus { + let vcpu = FakeVcpu { + id: i, + requested: Arc::new(Mutex::new(false)), + ready: Arc::new(Mutex::new(true)), + injected: Arc::new(Mutex::new(None)), + }; + chip.add_vcpu(i, &vcpu).expect("failed to add vcpu"); + chip.apics[i].lock().set_enabled(true); + } + + chip +} + +/// Helper function for cloning vcpus from a UserspaceIrqChip. +fn get_vcpus(chip: &UserspaceIrqChip) -> Vec { + chip.vcpus + .lock() + .iter() + .map(|v| v.as_ref().unwrap().try_clone().unwrap()) + .collect() +} + +#[test] +fn set_pic() { + test_set_pic(get_chip(1)); +} + +#[test] +fn get_ioapic() { + test_get_ioapic(get_chip(1)); +} + +#[test] +fn set_ioapic() { + test_set_ioapic(get_chip(1)); +} + +#[test] +fn get_pit() { + test_get_pit(get_chip(1)); +} + +#[test] +fn set_pit() { + test_set_pit(get_chip(1)); +} + +#[test] +fn route_irq() { + test_route_irq(get_chip(1)); +} + +#[test] +fn pit_uses_speaker_port() { + let chip = get_chip(1); + assert!(chip.pit_uses_speaker_port()); +} + +#[test] +fn routes_conflict() { + let mut chip = get_chip(1); + chip.route_irq(IrqRoute { + gsi: 32, + source: IrqSource::Msi { + address: 4276092928, + data: 0, + }, + }) + .expect("failed to set msi rout"); + // this second route should replace the first + chip.route_irq(IrqRoute { + gsi: 32, + source: IrqSource::Msi { + address: 4276092928, + data: 32801, + }, + }) + .expect("failed to set msi rout"); +} + +#[test] +fn irq_event_tokens() { + let mut chip = get_chip(1); + let tokens = chip + .irq_event_tokens() + .expect("could not get irq_event_tokens"); + + // there should be one token on a fresh split irqchip, for the pit + assert_eq!(tokens.len(), 1); + assert_eq!(tokens[0].1.device_name, "userspace PIT"); + + // register another irq event + let evt = IrqEdgeEvent::new().expect("failed to create eventfd"); + let source = IrqEventSource { + device_id: CrosvmDeviceId::Cmos.into(), + queue_id: 0, + device_name: "test".to_owned(), + }; + chip.register_edge_irq_event(6, &evt, source) + .expect("failed to register irq event"); + + let tokens = chip + .irq_event_tokens() + .expect("could not get irq_event_tokens"); + + // now there should be two tokens + assert_eq!(tokens.len(), 2); + assert_eq!(tokens[0].1.device_name, "userspace PIT"); + assert_eq!( + tokens[1].1.device_id, + DeviceId::PlatformDeviceId(CrosvmDeviceId::Cmos) + ); + assert_eq!(tokens[1].2, evt.get_trigger().try_clone().unwrap()); +} + +// TODO(srichman): Factor out of UserspaceIrqChip and KvmSplitIrqChip. +#[test] +fn finalize_devices() { + let mut chip = get_chip(1); + + let mmio_bus = Bus::new(); + let io_bus = Bus::new(); + let mut resources = SystemAllocator::new( + SystemAllocatorConfig { + io: Some(AddressRange { + start: 0xc000, + end: 0xFFFF, + }), + low_mmio: AddressRange { + start: 0, + end: 2047, + }, + high_mmio: AddressRange { + start: 2048, + end: 6143, + }, + platform_mmio: None, + first_irq: 5, + }, + None, + &[], + ) + .expect("failed to create SystemAllocator"); + + // Setup an event and a resample event for irq line 1. + let evt = IrqLevelEvent::new().expect("failed to create event"); + + let source = IrqEventSource { + device_id: CrosvmDeviceId::Cmos.into(), + device_name: "test".to_owned(), + queue_id: 0, + }; + + let evt_index = chip + .register_level_irq_event(1, &evt, source) + .expect("failed to register_level_irq_event") + .expect("register_level_irq_event should not return None"); + + // Once we finalize devices, the pic/pit/ioapic should be attached to io and mmio busses. + chip.finalize_devices(&mut resources, &io_bus, &mmio_bus) + .expect("failed to finalize devices"); + + // Should not be able to allocate an irq < 24 now. + assert!(resources.allocate_irq().expect("failed to allocate irq") >= 24); + + // Set PIT counter 2 to "SquareWaveGen" (aka 3) mode and "Both" access mode. + io_bus.write(0x43, &[0b10110110]); + + let state = chip.get_pit().expect("failed to get pit state"); + assert_eq!(state.channels[2].mode, 3); + assert_eq!(state.channels[2].rw_mode, PitRWMode::Both); + + // ICW1 0x11: Edge trigger, cascade mode, ICW4 needed. + // ICW2 0x08: Interrupt vector base address 0x08. + // ICW3 0xff: Value written does not matter. + // ICW4 0x13: Special fully nested mode, auto EOI. + io_bus.write(0x20, &[0x11]); + io_bus.write(0x21, &[0x08]); + io_bus.write(0x21, &[0xff]); + io_bus.write(0x21, &[0x13]); + + let state = chip + .get_pic_state(PicSelect::Primary) + .expect("failed to get pic state"); + + // Auto eoi and special fully nested mode should be turned on. + assert!(state.auto_eoi); + assert!(state.special_fully_nested_mode); + + // Need to write to the irq event before servicing it. + evt.trigger().expect("failed to write to eventfd"); + + // If we assert irq line one, and then get the resulting interrupt, an auto-eoi should occur + // and cause the resample_event to be written to. + chip.service_irq_event(evt_index) + .expect("failed to service irq"); + + let vcpu = get_vcpus(&chip).remove(0); + chip.inject_interrupts(&vcpu).unwrap(); + assert_eq!( + vcpu.clear_injected(), + // Vector is 9 because the interrupt vector base address is 0x08 and this is irq line 1 + // and 8+1 = 9. + Some(0x9) + ); + + assert_eq!( + evt.get_resample() + .wait_timeout(std::time::Duration::from_secs(1)) + .expect("failed to read_timeout"), + EventWaitResult::Signaled + ); + + // Setup a ioapic redirection table entry 14. + let mut entry = IoapicRedirectionTableEntry::default(); + entry.set_vector(44); + + let irq_14_offset = 0x10 + 14 * 2; + mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_14_offset]); + mmio_bus.write( + IOAPIC_BASE_ADDRESS + 0x10, + &(entry.get(0, 32) as u32).to_ne_bytes(), + ); + mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_14_offset + 1]); + mmio_bus.write( + IOAPIC_BASE_ADDRESS + 0x10, + &(entry.get(32, 32) as u32).to_ne_bytes(), + ); + + let state = chip.get_ioapic_state().expect("failed to get ioapic state"); + + // Redirection table entry 14 should have a vector of 44. + assert_eq!(state.redirect_table[14].get_vector(), 44); +} + +#[test] +fn inject_pic_interrupt() { + let mut chip = get_chip(2); + let vcpus = get_vcpus(&chip); + + assert_eq!(vcpus[0].clear_injected(), None); + assert_eq!(vcpus[1].clear_injected(), None); + + chip.service_irq(0, true).expect("failed to service irq"); + + // Should not inject PIC interrupt for vcpu_id != 0. + chip.inject_interrupts(&vcpus[1]).unwrap(); + assert_eq!(vcpus[1].clear_injected(), None); + + // Should inject Some interrupt. + chip.inject_interrupts(&vcpus[0]).unwrap(); + assert_eq!(vcpus[0].clear_injected(), Some(0)); + + // Interrupt is not injected twice. + chip.inject_interrupts(&vcpus[0]).unwrap(); + assert_eq!(vcpus[0].clear_injected(), None); +} + +#[test] +fn inject_msi() { + let mut chip = get_chip(2); + let vcpus = get_vcpus(&chip); + + let evt = IrqEdgeEvent::new().unwrap(); + let source = IrqEventSource { + device_id: CrosvmDeviceId::Cmos.into(), + device_name: "test".to_owned(), + queue_id: 0, + }; + let event_idx = chip + .register_edge_irq_event(30, &evt, source) + .unwrap() + .unwrap(); + chip.route_irq(IrqRoute { + gsi: 30, + source: IrqSource::Msi { + address: 0xFEE01000, // physical addressing, send to apic 1 + data: 0x000000F1, // edge-triggered, fixed interrupt, vector 0xF1 + }, + }) + .unwrap(); + evt.trigger().unwrap(); + + assert!(!vcpus[0].window_requested()); + assert!(!vcpus[1].window_requested()); + chip.service_irq_event(event_idx).unwrap(); + assert!(!vcpus[0].window_requested()); + assert!(vcpus[1].window_requested()); + + chip.inject_interrupts(&vcpus[0]).unwrap(); + assert_eq!(vcpus[0].clear_injected(), None); + + vcpus[1].set_ready(false); + chip.inject_interrupts(&vcpus[1]).unwrap(); + assert_eq!(vcpus[1].clear_injected(), None); + vcpus[1].set_ready(true); + chip.inject_interrupts(&vcpus[1]).unwrap(); + assert_eq!(vcpus[1].clear_injected(), Some(0xF1)); + assert!(!vcpus[1].window_requested()); +} + +#[test] +fn lowest_priority_destination() { + let chip = get_chip(2); + let vcpus = get_vcpus(&chip); + + // Make vcpu 0 higher priority. + chip.write( + BusAccessInfo { + id: 0, + address: APIC_BASE_ADDRESS + TPR, + offset: TPR, + }, + &[0x10, 0, 0, 0], + ); + chip.send_irq_to_apics(&Interrupt { + dest: InterruptDestination { + source_id: 0, + dest_id: 0, + shorthand: DestinationShorthand::All, + mode: DestinationMode::Physical, + }, + data: InterruptData { + vector: 111, + delivery: DeliveryMode::Lowest, + trigger: TriggerMode::Edge, + level: Level::Deassert, + }, + }); + chip.inject_interrupts(&vcpus[0]).unwrap(); + chip.inject_interrupts(&vcpus[1]).unwrap(); + assert_eq!(vcpus[0].clear_injected(), None); + assert_eq!(vcpus[1].clear_injected(), Some(111)); + chip.write( + BusAccessInfo { + id: 1, + address: APIC_BASE_ADDRESS + EOI, + offset: EOI, + }, + &[0, 0, 0, 0], + ); + + // Make vcpu 1 higher priority. + chip.write( + BusAccessInfo { + id: 1, + address: APIC_BASE_ADDRESS + TPR, + offset: TPR, + }, + &[0x20, 0, 0, 0], + ); + chip.send_irq_to_apics(&Interrupt { + dest: InterruptDestination { + source_id: 0, + dest_id: 0, + shorthand: DestinationShorthand::All, + mode: DestinationMode::Physical, + }, + data: InterruptData { + vector: 222, + delivery: DeliveryMode::Lowest, + trigger: TriggerMode::Edge, + level: Level::Deassert, + }, + }); + chip.inject_interrupts(&vcpus[0]).unwrap(); + chip.inject_interrupts(&vcpus[1]).unwrap(); + assert_eq!(vcpus[0].clear_injected(), Some(222)); + assert_eq!(vcpus[1].clear_injected(), None); +} + +// TODO(srichman): Factor out of UserspaceIrqChip and KvmSplitIrqChip. +#[test] +fn broadcast_eoi() { + let mut chip = get_chip(1); + + let mmio_bus = Bus::new(); + let io_bus = Bus::new(); + let mut resources = SystemAllocator::new( + SystemAllocatorConfig { + io: Some(AddressRange { + start: 0xc000, + end: 0xFFFF, + }), + low_mmio: AddressRange { + start: 0, + end: 2047, + }, + high_mmio: AddressRange { + start: 2048, + end: 6143, + }, + platform_mmio: None, + first_irq: 5, + }, + None, + &[], + ) + .expect("failed to create SystemAllocator"); + + // setup an event for irq line 1 + let evt = IrqLevelEvent::new().expect("failed to create event"); + + let source = IrqEventSource { + device_id: CrosvmDeviceId::Cmos.into(), + device_name: "test".to_owned(), + queue_id: 0, + }; + + chip.register_level_irq_event(1, &evt, source) + .expect("failed to register_level_irq_event"); + + // Once we finalize devices, the pic/pit/ioapic should be attached to io and mmio busses + chip.finalize_devices(&mut resources, &io_bus, &mmio_bus) + .expect("failed to finalize devices"); + + // setup a ioapic redirection table entry 1 with a vector of 123 + let mut entry = IoapicRedirectionTableEntry::default(); + entry.set_vector(123); + entry.set_trigger_mode(TriggerMode::Level); + + let irq_write_offset = 0x10 + 1 * 2; + mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_write_offset]); + mmio_bus.write( + IOAPIC_BASE_ADDRESS + 0x10, + &(entry.get(0, 32) as u32).to_ne_bytes(), + ); + mmio_bus.write(IOAPIC_BASE_ADDRESS, &[irq_write_offset + 1]); + mmio_bus.write( + IOAPIC_BASE_ADDRESS + 0x10, + &(entry.get(32, 32) as u32).to_ne_bytes(), + ); + + // Assert line 1 + chip.service_irq(1, true).expect("failed to service irq"); + + // resample event should not be written to + assert_eq!( + evt.get_resample() + .wait_timeout(std::time::Duration::from_millis(10)) + .expect("failed to read_timeout"), + EventWaitResult::TimedOut + ); + + // irq line 1 should be asserted + let state = chip.get_ioapic_state().expect("failed to get ioapic state"); + assert_eq!(state.current_interrupt_level_bitmap, 1 << 1); + + // Now broadcast an eoi for vector 123 + chip.broadcast_eoi(123).expect("failed to broadcast eoi"); + + // irq line 1 should be deasserted + let state = chip.get_ioapic_state().expect("failed to get ioapic state"); + assert_eq!(state.current_interrupt_level_bitmap, 0); + + // resample event should be written to by ioapic + assert_eq!( + evt.get_resample() + .wait_timeout(std::time::Duration::from_millis(10)) + .expect("failed to read_timeout"), + EventWaitResult::Signaled + ); +} + +#[test] +fn apic_mmio() { + let chip = get_chip(2); + let mut data = [0u8; 4]; + chip.read( + BusAccessInfo { + id: 0, + address: APIC_BASE_ADDRESS + APIC_ID, + offset: APIC_ID, + }, + &mut data, + ); + assert_eq!(data, [0, 0, 0, 0]); + chip.read( + BusAccessInfo { + id: 1, + address: APIC_BASE_ADDRESS + APIC_ID, + offset: APIC_ID, + }, + &mut data, + ); + assert_eq!(data, [0, 0, 0, 1]); +} + +#[test] +#[ignore = "TODO(b/237977699): remove reliance on sleep"] +fn runnable_vcpu_unhalts() { + let chip = get_chip(1); + let vcpu = get_vcpus(&chip).remove(0); + let chip_copy = chip.try_clone().unwrap(); + // BSP starts runnable. + assert_eq!(chip.wait_until_runnable(&vcpu), Ok(VcpuRunState::Runnable)); + let start = Instant::now(); + let handle = thread::spawn(move || { + thread::sleep(TEST_SLEEP_DURATION); + chip_copy.send_irq_to_apic( + 0, + &InterruptData { + vector: 123, + delivery: DeliveryMode::Fixed, + trigger: TriggerMode::Level, + level: Level::Assert, + }, + ); + }); + chip.halted(0); + assert_eq!(chip.wait_until_runnable(&vcpu), Ok(VcpuRunState::Runnable)); + assert!(Instant::now() - start > Duration::from_millis(5)); + handle.join().unwrap(); +} + +#[test] +#[ignore = "TODO(b/237977699): remove reliance on sleep"] +fn kicked_vcpu_unhalts() { + let chip = get_chip(1); + let vcpu = get_vcpus(&chip).remove(0); + let chip_copy = chip.try_clone().unwrap(); + // BSP starts runnable. + assert_eq!(chip.wait_until_runnable(&vcpu), Ok(VcpuRunState::Runnable)); + let start = Instant::now(); + let handle = thread::spawn(move || { + thread::sleep(TEST_SLEEP_DURATION); + chip_copy.kick_halted_vcpus(); + }); + chip.halted(0); + assert_eq!( + chip.wait_until_runnable(&vcpu), + Ok(VcpuRunState::Interrupted) + ); + assert!(Instant::now() - start > Duration::from_millis(5)); + handle.join().unwrap(); +} + +/// Mock vcpu for testing interrupt injection. +struct FakeVcpu { + id: usize, + requested: Arc>, + ready: Arc>, + injected: Arc>>, +} + +impl FakeVcpu { + /// Returns and clears the last interrupt set by `interrupt`. + fn clear_injected(&self) -> Option { + self.injected.lock().take() + } + + /// Returns true if an interrupt window was requested with `set_interrupt_window_requested`. + fn window_requested(&self) -> bool { + *self.requested.lock() + } + + /// Sets the value to be returned by `ready_for_interrupt`. + fn set_ready(&self, val: bool) { + *self.ready.lock() = val; + } +} + +impl Vcpu for FakeVcpu { + fn try_clone(&self) -> Result { + Ok(FakeVcpu { + id: self.id, + requested: self.requested.clone(), + ready: self.ready.clone(), + injected: self.injected.clone(), + }) + } + + fn id(&self) -> usize { + self.id + } + + fn as_vcpu(&self) -> &dyn Vcpu { + self + } + + fn take_run_handle(&self, _signal_num: Option) -> Result { + unimplemented!() + } + fn run(&mut self, _run_handle: &VcpuRunHandle) -> Result { + unimplemented!() + } + fn set_immediate_exit(&self, _exit: bool) {} + fn set_local_immediate_exit(_exit: bool) {} + fn set_local_immediate_exit_fn(&self) -> extern "C" fn() { + extern "C" fn f() { + FakeVcpu::set_local_immediate_exit(true); + } + f + } + fn handle_mmio(&self, _handle_fn: &mut dyn FnMut(IoParams) -> Option<[u8; 8]>) -> Result<()> { + unimplemented!() + } + fn handle_io(&self, _handle_fn: &mut dyn FnMut(IoParams) -> Option<[u8; 8]>) -> Result<()> { + unimplemented!() + } + fn handle_hyperv_hypercall(&self, _func: &mut dyn FnMut(HypervHypercall) -> u64) -> Result<()> { + unimplemented!() + } + fn handle_rdmsr(&self, _data: u64) -> Result<()> { + unimplemented!() + } + fn handle_wrmsr(&self) { + unimplemented!() + } + fn pvclock_ctrl(&self) -> Result<()> { + unimplemented!() + } + fn set_signal_mask(&self, _signals: &[c_int]) -> Result<()> { + unimplemented!() + } + unsafe fn enable_raw_capability(&self, _cap: u32, _args: &[u64; 4]) -> Result<()> { + unimplemented!() + } +} + +impl VcpuX86_64 for FakeVcpu { + fn set_interrupt_window_requested(&self, requested: bool) { + *self.requested.lock() = requested; + } + + fn ready_for_interrupt(&self) -> bool { + *self.ready.lock() + } + + fn interrupt(&self, irq: u32) -> Result<()> { + *self.injected.lock() = Some(irq); + Ok(()) + } + + fn inject_nmi(&self) -> Result<()> { + Ok(()) + } + + fn get_regs(&self) -> Result { + unimplemented!() + } + fn set_regs(&self, _regs: &Regs) -> Result<()> { + unimplemented!() + } + fn get_sregs(&self) -> Result { + unimplemented!() + } + fn set_sregs(&self, _sregs: &Sregs) -> Result<()> { + unimplemented!() + } + fn get_fpu(&self) -> Result { + unimplemented!() + } + fn set_fpu(&self, _fpu: &Fpu) -> Result<()> { + unimplemented!() + } + fn get_debugregs(&self) -> Result { + unimplemented!() + } + fn set_debugregs(&self, _debugregs: &DebugRegs) -> Result<()> { + unimplemented!() + } + fn get_xcrs(&self) -> Result> { + unimplemented!() + } + fn set_xcrs(&self, _xcrs: &[Register]) -> Result<()> { + unimplemented!() + } + fn get_msrs(&self, _msrs: &mut Vec) -> Result<()> { + unimplemented!() + } + fn set_msrs(&self, _msrs: &[Register]) -> Result<()> { + unimplemented!() + } + fn set_cpuid(&self, _cpuid: &CpuId) -> Result<()> { + unimplemented!() + } + fn handle_cpuid(&mut self, _entry: &CpuIdEntry) -> Result<()> { + unimplemented!() + } + fn get_hyperv_cpuid(&self) -> Result { + unimplemented!() + } + fn set_guest_debug(&self, _addrs: &[GuestAddress], _enable_singlestep: bool) -> Result<()> { + unimplemented!() + } + fn get_tsc_offset(&self) -> Result { + unimplemented!() + } + fn set_tsc_offset(&self, _offset: u64) -> Result<()> { + unimplemented!() + } +} diff --git a/devices/tests/irqchip/x86_64.rs b/devices/tests/irqchip/x86_64.rs new file mode 100644 index 0000000000..c20e47b7c1 --- /dev/null +++ b/devices/tests/irqchip/x86_64.rs @@ -0,0 +1,283 @@ +// Copyright 2022 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#![cfg(any(target_arch = "x86", target_arch = "x86_64"))] + +use devices::IrqChipX86_64; +use devices::Routes; +use hypervisor::IrqRoute; +use hypervisor::IrqSource; +use hypervisor::IrqSourceChip; +use hypervisor::PicSelect; + +pub fn test_get_pic(mut chip: impl IrqChipX86_64) { + 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 + chip.service_irq(0, true).expect("could not service irq"); + + 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); +} + +pub fn test_set_pic(mut chip: impl IrqChipX86_64) { + 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); +} + +pub fn test_get_ioapic(mut chip: impl IrqChipX86_64) { + 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 + chip.service_irq(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); +} + +pub fn test_set_ioapic(mut chip: impl IrqChipX86_64) { + 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); +} + +pub fn test_get_pit(chip: impl IrqChipX86_64) { + 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); + } +} + +pub fn test_set_pit(mut chip: impl IrqChipX86_64) { + 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); +} + +pub fn test_get_lapic(chip: impl IrqChipX86_64) { + let state = chip.get_lapic_state(0).expect("failed to get lapic state"); + + // Checking some APIC reg defaults for KVM: + // DFR default is 0xffffffff + assert_eq!(state.regs[0xe], 0xffffffff); + // SPIV default is 0xff + assert_eq!(state.regs[0xf], 0xff); +} + +pub fn test_set_lapic(mut chip: impl IrqChipX86_64) { + // Get default state + let mut state = chip.get_lapic_state(0).expect("failed to get lapic state"); + + // ESR should start out as 0 + assert_eq!(state.regs[8], 0); + // Set a value in the ESR + state.regs[8] = 1 << 8; + chip.set_lapic_state(0, &state) + .expect("failed to set lapic state"); + + // check that new ESR value stuck + let state = chip.get_lapic_state(0).expect("failed to get lapic state"); + assert_eq!(state.regs[8], 1 << 8); +} + +/// Helper function for checking the pic interrupt status +fn check_pic_interrupts(chip: &impl IrqChipX86_64, select: PicSelect, value: u8) { + let state = chip + .get_pic_state(select) + .expect("could not get ioapic state"); + + assert_eq!(state.irr, value); +} + +/// Helper function for checking the ioapic interrupt status +fn check_ioapic_interrupts(chip: &impl IrqChipX86_64, value: u32) { + let state = chip.get_ioapic_state().expect("could not get ioapic state"); + + // since the irq route goes nowhere the bitmap should still be 0 + assert_eq!(state.current_interrupt_level_bitmap, value); +} + +pub fn test_route_irq(mut chip: impl IrqChipX86_64) { + // clear out irq routes + chip.set_irq_routes(&[]) + .expect("failed to set empty irq routes"); + // assert Irq Line 1 + chip.service_irq(1, true).expect("could not set irq line"); + + // no pic or ioapic interrupts should be asserted + check_pic_interrupts(&chip, PicSelect::Primary, 0); + check_ioapic_interrupts(&chip, 0); + + // now we route gsi 1 to pin 3 of the ioapic and pin 6 of the primary pic + chip.route_irq(IrqRoute { + gsi: 1, + source: IrqSource::Irqchip { + chip: IrqSourceChip::Ioapic, + pin: 3, + }, + }) + .expect("failed to assert irq route"); + // re-assert Irq Line 1 + chip.service_irq(1, true).expect("could not set irq line"); + + // no pic line should be asserted, ioapic pin 3 should be asserted + check_pic_interrupts(&chip, PicSelect::Primary, 0); + check_ioapic_interrupts(&chip, 1 << 3); + + // de-assert Irq Line 1 + chip.service_irq(1, false).expect("could not set irq line"); + + // no pic or ioapic interrupts should be asserted + check_pic_interrupts(&chip, PicSelect::Primary, 0); + check_ioapic_interrupts(&chip, 0); + + // add pic route + chip.route_irq(IrqRoute { + gsi: 2, + source: IrqSource::Irqchip { + chip: IrqSourceChip::PicPrimary, + pin: 6, + }, + }) + .expect("failed to route irq"); + + // re-assert Irq Line 1, it should still affect only the ioapic + chip.service_irq(1, true).expect("could not set irq line"); + + // no pic line should be asserted, ioapic pin 3 should be asserted + check_pic_interrupts(&chip, PicSelect::Primary, 0); + check_ioapic_interrupts(&chip, 1 << 3); + + // assert Irq Line 2 + chip.service_irq(2, true).expect("could not set irq line"); + + // pic pin 6 should be asserted, ioapic pin 3 should be asserted + check_pic_interrupts(&chip, PicSelect::Primary, 1 << 6); + check_ioapic_interrupts(&chip, 1 << 3); +} + +#[test] +fn add_routes() { + let ioapic_pins = hypervisor::NUM_IOAPIC_PINS; + let mut r = Routes::new(); + r.replace_all(&Routes::default_pic_ioapic_routes(ioapic_pins)) + .unwrap(); + + assert_eq!(r[0].len(), 2); + + assert_eq!(r[ioapic_pins - 1].len(), 1); + r.add(IrqRoute { + gsi: ioapic_pins as u32 - 1, + source: IrqSource::Irqchip { + chip: IrqSourceChip::Ioapic, + pin: 3, + }, + }) + .unwrap(); + assert_eq!(r[ioapic_pins - 1].len(), 1); + r.add(IrqRoute { + gsi: ioapic_pins as u32 - 1, + source: IrqSource::Irqchip { + chip: IrqSourceChip::PicPrimary, + pin: 3, + }, + }) + .unwrap(); + assert_eq!(r[ioapic_pins - 1].len(), 2); + assert!(r + .add(IrqRoute { + gsi: ioapic_pins as u32 - 1, + source: IrqSource::Msi { + address: 0, + data: 0 + }, + }) + .is_err(),); + assert_eq!(r[ioapic_pins - 1].len(), 2); + + assert_eq!(r[ioapic_pins].len(), 0); + r.add(IrqRoute { + gsi: ioapic_pins as u32, + source: IrqSource::Msi { + address: 0, + data: 0, + }, + }) + .unwrap(); + assert_eq!(r[ioapic_pins].len(), 1); + assert!(r + .add(IrqRoute { + gsi: ioapic_pins as u32, + source: IrqSource::Irqchip { + chip: IrqSourceChip::Ioapic, + pin: 3 + }, + }) + .is_err(),); + assert_eq!(r[ioapic_pins].len(), 1); + + assert_eq!(r[500].len(), 0); +} diff --git a/tools/impl/test_config.py b/tools/impl/test_config.py index 3d3520e3ce..d32f40c54a 100755 --- a/tools/impl/test_config.py +++ b/tools/impl/test_config.py @@ -85,14 +85,6 @@ CRATE_OPTIONS: Dict[str, List[TestOption]] = { TestOption.DO_NOT_BUILD_ARMHF, ], "crosvm-fuzz": [TestOption.DO_NOT_BUILD], # b/194499769 - "devices": [ - TestOption.SINGLE_THREADED, - TestOption.RUN_EXCLUSIVE, - TestOption.LARGE, - TestOption.DO_NOT_RUN_ON_FOREIGN_KERNEL, - TestOption.DO_NOT_RUN_ARMHF, - TestOption.UNIT_AS_INTEGRATION_TEST, - ], "disk": [TestOption.DO_NOT_RUN_AARCH64, TestOption.DO_NOT_RUN_ARMHF], # b/202294155 "ffmpeg": [TestOption.DO_NOT_BUILD_ARMHF], "cros-fuzz": [TestOption.DO_NOT_BUILD],