mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-24 12:34:31 +00:00
7eb6a6ba6e
TEST=cargo nextest run --workspace --features=all-msvc64,whpx Change-Id: I257f95920518975500ca751ef555cd300be321a8 Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/5567151 Reviewed-by: Richard Zhang <rizhang@google.com> Commit-Queue: Kaiyi Li <kaiyili@google.com>
408 lines
12 KiB
Rust
408 lines
12 KiB
Rust
// 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(target_arch = "x86_64")]
|
|
|
|
use base::EventWaitResult;
|
|
use base::Tube;
|
|
use devices::Bus;
|
|
use devices::BusType;
|
|
use devices::CrosvmDeviceId;
|
|
use devices::DeviceId;
|
|
use devices::IrqChip;
|
|
use devices::IrqChipX86_64;
|
|
use devices::IrqEdgeEvent;
|
|
use devices::IrqEventSource;
|
|
use devices::IrqLevelEvent;
|
|
use devices::WhpxSplitIrqChip;
|
|
use devices::IOAPIC_BASE_ADDRESS;
|
|
use hypervisor::whpx::Whpx;
|
|
use hypervisor::whpx::WhpxFeature;
|
|
use hypervisor::whpx::WhpxVm;
|
|
use hypervisor::CpuId;
|
|
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::GuestAddress;
|
|
use vm_memory::GuestMemory;
|
|
|
|
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;
|
|
|
|
fn split_supported() -> bool {
|
|
Whpx::check_whpx_feature(WhpxFeature::LocalApicEmulation).expect("failed to get whpx features")
|
|
}
|
|
|
|
/// Helper function for setting up a WhpxSplitIrqChip.
|
|
fn get_chip(num_vcpus: usize) -> WhpxSplitIrqChip {
|
|
let whpx = Whpx::new().expect("failed to instantiate Whpx");
|
|
let mem = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
|
|
let vm = WhpxVm::new(&whpx, num_vcpus, mem, CpuId::new(0), true, None)
|
|
.expect("failed to instantiate vm");
|
|
|
|
let (_, irq_tube) = Tube::pair().expect("failed to create irq tube");
|
|
|
|
let mut chip =
|
|
WhpxSplitIrqChip::new(vm.try_clone().expect("failed to clone vm"), irq_tube, None)
|
|
.expect("failed to instantiate WhpxSplitIrqChip");
|
|
|
|
for i in 0..num_vcpus {
|
|
let vcpu = vm.create_vcpu(i).expect("failed to instantiate vcpu");
|
|
chip.add_vcpu(i, vcpu.as_vcpu())
|
|
.expect("failed to add vcpu");
|
|
}
|
|
|
|
chip
|
|
}
|
|
|
|
#[test]
|
|
fn set_pic() {
|
|
if !split_supported() {
|
|
return;
|
|
}
|
|
test_set_pic(get_chip(1));
|
|
}
|
|
|
|
#[test]
|
|
fn get_ioapic() {
|
|
if !split_supported() {
|
|
return;
|
|
}
|
|
test_get_ioapic(get_chip(1));
|
|
}
|
|
|
|
#[test]
|
|
fn set_ioapic() {
|
|
if !split_supported() {
|
|
return;
|
|
}
|
|
test_set_ioapic(get_chip(1));
|
|
}
|
|
|
|
#[test]
|
|
fn get_pit() {
|
|
if !split_supported() {
|
|
return;
|
|
}
|
|
test_get_pit(get_chip(1));
|
|
}
|
|
|
|
#[test]
|
|
fn set_pit() {
|
|
if !split_supported() {
|
|
return;
|
|
}
|
|
test_set_pit(get_chip(1));
|
|
}
|
|
|
|
#[test]
|
|
fn route_irq() {
|
|
if !split_supported() {
|
|
return;
|
|
}
|
|
test_route_irq(get_chip(1));
|
|
}
|
|
|
|
#[test]
|
|
fn pit_uses_speaker_port() {
|
|
if !split_supported() {
|
|
return;
|
|
}
|
|
let chip = get_chip(1);
|
|
assert!(chip.pit_uses_speaker_port());
|
|
}
|
|
|
|
#[test]
|
|
fn routes_conflict() {
|
|
if !split_supported() {
|
|
return;
|
|
}
|
|
let mut chip = get_chip(1);
|
|
chip.route_irq(IrqRoute {
|
|
gsi: 32,
|
|
source: IrqSource::Msi {
|
|
address: 4276092928,
|
|
data: 0,
|
|
},
|
|
})
|
|
.expect("failed to set msi route");
|
|
// 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 route");
|
|
}
|
|
|
|
#[test]
|
|
fn irq_event_tokens() {
|
|
if !split_supported() {
|
|
return;
|
|
}
|
|
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 event");
|
|
let source = IrqEventSource {
|
|
device_id: CrosvmDeviceId::DebugConsole.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::DebugConsole)
|
|
);
|
|
assert_eq!(tokens[1].2, evt.get_trigger().try_clone().unwrap());
|
|
}
|
|
|
|
// TODO(srichman): Factor out of UserspaceIrqChip and KvmSplitIrqChip and WhpxSplitIrqChip.
|
|
#[test]
|
|
fn finalize_devices() {
|
|
if !split_supported() {
|
|
return;
|
|
}
|
|
let mut chip = get_chip(1);
|
|
|
|
let mmio_bus = Bus::new(BusType::Mmio);
|
|
let io_bus = Bus::new(BusType::Io);
|
|
let mut resources = SystemAllocator::new(
|
|
SystemAllocatorConfig {
|
|
io: Some(AddressRange {
|
|
start: 0xc000,
|
|
end: 0xFFFF,
|
|
}),
|
|
low_mmio: AddressRange {
|
|
start: 0,
|
|
end: 2048,
|
|
},
|
|
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::DebugConsole.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 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
|
|
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);
|
|
}
|
|
|
|
// TODO(srichman): Factor out of UserspaceIrqChip and KvmSplitIrqChip and WhpxSplitIrqChip.
|
|
#[test]
|
|
fn broadcast_eoi() {
|
|
if !split_supported() {
|
|
return;
|
|
}
|
|
let mut chip = get_chip(1);
|
|
|
|
let mmio_bus = Bus::new(BusType::Mmio);
|
|
let io_bus = Bus::new(BusType::Io);
|
|
let mut resources = SystemAllocator::new(
|
|
SystemAllocatorConfig {
|
|
io: Some(AddressRange {
|
|
start: 0xc000,
|
|
end: 0xFFFF,
|
|
}),
|
|
low_mmio: AddressRange {
|
|
start: 0,
|
|
end: 2048,
|
|
},
|
|
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::DebugConsole.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
|
|
);
|
|
}
|