mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-09 03:57:24 +00:00
Extract devices integration tests
This change moves most ircchip tests into an integration test. These tests rely on kvm, and if they do not - reuse much of the test code from each other, so they need to move together. Note: This removes the apic_timer test. The test has been disabled for a while due to it's use of sleep in unit tests. We cannot support it as an integration test either, since it combines the sleep with a FakeClock. userspace.rs swaps the real clock for FakeClock when compiled with cfg(test), but integration tests do not compile with cfg(test), so we cannot use the FakeClock. The biggest side-effect is faster execution as we can run all other 300+ tests in parallel and via user-space emulation, significantly cutting down on the test times. It also allows those tests to run in a podman container. BUG=b:244620308 TEST=CQ Change-Id: I1728a736d27e924daf228752711435885dacfa6a Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3977111 Reviewed-by: Daniel Verkamp <dverkamp@chromium.org> Commit-Queue: Dennis Kempin <denniskempin@google.com>
This commit is contained in:
parent
e288105e56
commit
2f5eb3ac64
11 changed files with 1585 additions and 1678 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<u32> {
|
||||
pub fn get_external_interrupt(&self, vcpu_id: usize) -> Option<u32> {
|
||||
// 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<V: VcpuX86_64> {
|
||||
vcpus: Arc<Mutex<Vec<Option<V>>>>,
|
||||
pub vcpus: Arc<Mutex<Vec<Option<V>>>>,
|
||||
routes: Arc<Mutex<Routes>>,
|
||||
pit: Arc<Mutex<Pit>>,
|
||||
pic: Arc<Mutex<Pic>>,
|
||||
ioapic: Arc<Mutex<Ioapic>>,
|
||||
ioapic_pins: usize,
|
||||
apics: Vec<Arc<Mutex<Apic>>>,
|
||||
pub apics: Vec<Arc<Mutex<Apic>>>,
|
||||
// Condition variables used by wait_until_runnable.
|
||||
waiters: Vec<Arc<Waiter>>,
|
||||
// Raw descriptors of the apic Timers.
|
||||
|
@ -240,7 +240,7 @@ impl<V: VcpuX86_64 + 'static> UserspaceIrqChip<V> {
|
|||
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<V: VcpuX86_64 + 'static> UserspaceIrqChip<V> {
|
|||
}
|
||||
|
||||
/// 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<T> = std::result::Result<T, TimerWorkerError>;
|
||||
|
||||
// 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<FakeVcpu> {
|
||||
get_chip_with_clock(num_vcpus, Arc::new(Mutex::new(Clock::new())))
|
||||
}
|
||||
|
||||
fn get_chip_with_clock(
|
||||
num_vcpus: usize,
|
||||
clock: Arc<Mutex<Clock>>,
|
||||
) -> UserspaceIrqChip<FakeVcpu> {
|
||||
let (_, irq_tube) = Tube::pair().unwrap();
|
||||
let mut chip =
|
||||
UserspaceIrqChip::<FakeVcpu>::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<FakeVcpu>) -> Vec<FakeVcpu> {
|
||||
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<Mutex<bool>>,
|
||||
ready: Arc<Mutex<bool>>,
|
||||
injected: Arc<Mutex<Option<u32>>>,
|
||||
}
|
||||
|
||||
impl FakeVcpu {
|
||||
/// Returns and clears the last interrupt set by `interrupt`.
|
||||
fn clear_injected(&self) -> Option<u32> {
|
||||
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<Self> {
|
||||
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<c_int>) -> Result<VcpuRunHandle> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn run(&mut self, _run_handle: &VcpuRunHandle) -> Result<VcpuExit> {
|
||||
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<Regs> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_regs(&self, _regs: &Regs) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_sregs(&self) -> Result<Sregs> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_sregs(&self, _sregs: &Sregs) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_fpu(&self) -> Result<Fpu> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_fpu(&self, _fpu: &Fpu) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_debugregs(&self) -> Result<DebugRegs> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_debugregs(&self, _debugregs: &DebugRegs) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_xcrs(&self) -> Result<Vec<Register>> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_xcrs(&self, _xcrs: &[Register]) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_msrs(&self, _msrs: &mut Vec<Register>) -> 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<CpuId> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_guest_debug(&self, _addrs: &[GuestAddress], _enable_singlestep: bool) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_tsc_offset(&self) -> Result<u64> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_tsc_offset(&self, _offset: u64) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
|
|
57
devices/tests/irqchip/kvm/mod.rs
Normal file
57
devices/tests/irqchip/kvm/mod.rs
Normal file
|
@ -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);
|
||||
}
|
450
devices/tests/irqchip/kvm/x86_64.rs
Normal file
450
devices/tests/irqchip/kvm/x86_64.rs
Normal file
|
@ -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
|
||||
);
|
||||
}
|
7
devices/tests/irqchip/main.rs
Normal file
7
devices/tests/irqchip/main.rs
Normal file
|
@ -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;
|
779
devices/tests/irqchip/userspace.rs
Normal file
779
devices/tests/irqchip/userspace.rs
Normal file
|
@ -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<FakeVcpu> {
|
||||
get_chip_with_clock(num_vcpus, Arc::new(Mutex::new(Clock::new())))
|
||||
}
|
||||
|
||||
fn get_chip_with_clock(num_vcpus: usize, clock: Arc<Mutex<Clock>>) -> UserspaceIrqChip<FakeVcpu> {
|
||||
let (_, irq_tube) = Tube::pair().unwrap();
|
||||
let mut chip = UserspaceIrqChip::<FakeVcpu>::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<FakeVcpu>) -> Vec<FakeVcpu> {
|
||||
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<Mutex<bool>>,
|
||||
ready: Arc<Mutex<bool>>,
|
||||
injected: Arc<Mutex<Option<u32>>>,
|
||||
}
|
||||
|
||||
impl FakeVcpu {
|
||||
/// Returns and clears the last interrupt set by `interrupt`.
|
||||
fn clear_injected(&self) -> Option<u32> {
|
||||
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<Self> {
|
||||
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<c_int>) -> Result<VcpuRunHandle> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn run(&mut self, _run_handle: &VcpuRunHandle) -> Result<VcpuExit> {
|
||||
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<Regs> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_regs(&self, _regs: &Regs) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_sregs(&self) -> Result<Sregs> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_sregs(&self, _sregs: &Sregs) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_fpu(&self) -> Result<Fpu> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_fpu(&self, _fpu: &Fpu) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_debugregs(&self) -> Result<DebugRegs> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_debugregs(&self, _debugregs: &DebugRegs) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_xcrs(&self) -> Result<Vec<Register>> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_xcrs(&self, _xcrs: &[Register]) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_msrs(&self, _msrs: &mut Vec<Register>) -> 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<CpuId> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_guest_debug(&self, _addrs: &[GuestAddress], _enable_singlestep: bool) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_tsc_offset(&self) -> Result<u64> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_tsc_offset(&self, _offset: u64) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
283
devices/tests/irqchip/x86_64.rs
Normal file
283
devices/tests/irqchip/x86_64.rs
Normal file
|
@ -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);
|
||||
}
|
|
@ -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],
|
||||
|
|
Loading…
Reference in a new issue