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:
Dennis Kempin 2022-10-24 22:57:01 +00:00 committed by crosvm LUCI
parent e288105e56
commit 2f5eb3ac64
11 changed files with 1585 additions and 1678 deletions

View file

@ -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);
}
}

View file

@ -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
);
}
}

View file

@ -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!()
}
}
}

View file

@ -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);
}
}

View file

@ -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()));

View 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);
}

View 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
);
}

View 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;

View 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!()
}
}

View 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);
}

View file

@ -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],