devices: PIC: implement interrupt injection

TODO: Route irqfd to PIC, and use signal to kick vCPU thread when
interrupt is triggered.

BUG=chromium:908689
TEST=Unit tests in file.

Change-Id: I9a87502da57e725d3bb26d746a337d0ba44ef337
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1945797
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Zhuocheng Ding <zhuocheng.ding@intel.corp-partner.google.com>
This commit is contained in:
Zhuocheng Ding 2019-12-02 15:50:24 +08:00 committed by Commit Bot
parent f2e90bf0b0
commit db4c70d215
3 changed files with 58 additions and 7 deletions

View file

@ -7,10 +7,10 @@
// modern OSs that use a legacy BIOS.
// The PIC is connected to the Local APIC on CPU0.
// Terminology note: The 8259A spec refers to "master" and "slave" PITs; the "slave"s are daisy
// Terminology note: The 8259A spec refers to "master" and "slave" PICs; the "slave"s are daisy
// chained to the "master"s.
// For the purposes of both using more descriptive terms and avoiding terms with lots of charged
// emotional context, this file refers to them instead as "primary" and "secondary" PITs.
// emotional context, this file refers to them instead as "primary" and "secondary" PICs.
use crate::BusDevice;
use sys_util::{debug, warn};
@ -56,9 +56,9 @@ struct PicState {
}
pub struct Pic {
// TODO(mutexlox): Implement an APIC and add necessary state to the Pic.
// index 0 (aka PicSelect::Primary) is the primary pic, the rest are secondary.
// Indicates a pending INTR signal to LINT0 of vCPU, checked by vCPU thread.
interrupt_request: bool,
// Index 0 (aka PicSelect::Primary) is the primary pic, the rest are secondary.
pics: [PicState; 2],
}
@ -175,9 +175,9 @@ impl Pic {
// The secondary PIC has IRQs 8-15, so we subtract 8 from the IRQ number to get the bit
// that should be masked here. In this case, bits 8 - 8 = 0 and 13 - 8 = 5.
secondary_pic.elcr_mask = !((1 << 0) | (1 << 5));
// TODO(mutexlox): Add logic to initialize APIC interrupt-related fields.
Pic {
interrupt_request: false,
pics: [primary_pic, secondary_pic],
}
}
@ -205,8 +205,14 @@ impl Pic {
self.get_irq(PicSelect::Primary).is_some()
}
/// Determines whether the PIC has fired an interrupt to LAPIC.
pub fn interrupt_requested(&self) -> bool {
self.interrupt_request
}
/// Determines the external interrupt number that the PIC is prepared to inject, if any.
pub fn get_external_interrupt(&mut self) -> Option<u8> {
self.interrupt_request = false;
let irq_primary = if let Some(irq) = self.get_irq(PicSelect::Primary) {
irq
} else {
@ -403,7 +409,7 @@ impl Pic {
}
if self.get_irq(PicSelect::Primary).is_some() {
// TODO(mutexlox): Signal local interrupt on APIC bus.
self.interrupt_request = true;
// Note: this does not check if the interrupt is succesfully injected into
// the CPU, just whether or not one is fired.
true

View file

@ -1313,6 +1313,26 @@ impl Vcpu {
});
}
/// Request the VCPU to exit when it becomes possible to inject interrupts into the guest.
#[allow(clippy::cast_ptr_alignment)]
pub fn request_interrupt_window(&self) {
// Safe because we know we mapped enough memory to hold the kvm_run struct because the
// kernel told us how large it was. The pointer is page aligned so casting to a different
// type is well defined, hence the clippy allow attribute.
let run = unsafe { &mut *(self.run_mmap.as_ptr() as *mut kvm_run) };
run.request_interrupt_window = 1;
}
/// Checks if we can inject an interrupt into the VCPU.
#[allow(clippy::cast_ptr_alignment)]
pub fn ready_for_interrupt(&self) -> bool {
// Safe because we know we mapped enough memory to hold the kvm_run struct because the
// kernel told us how large it was. The pointer is page aligned so casting to a different
// type is well defined, hence the clippy allow attribute.
let run = unsafe { &mut *(self.run_mmap.as_ptr() as *mut kvm_run) };
run.ready_for_interrupt_injection != 0 && run.if_flag != 0
}
/// Gets the VCPU registers.
#[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
pub fn get_regs(&self) -> Result<kvm_regs> {

View file

@ -1349,6 +1349,26 @@ fn runnable_vcpu(vcpu: Vcpu, use_kvm_signals: bool, cpu_id: u32) -> Option<Runna
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn inject_interrupt(pic: &Arc<Mutex<devices::Pic>>, vcpu: &RunnableVcpu) {
let mut pic = pic.lock();
if pic.interrupt_requested() && vcpu.ready_for_interrupt() {
if let Some(vector) = pic.get_external_interrupt() {
if let Err(e) = vcpu.interrupt(vector as u32) {
error!("PIC: failed to inject interrupt to vCPU0: {}", e);
}
}
// The second interrupt request should be handled immediately, so ask
// vCPU to exit as soon as possible.
if pic.interrupt_requested() {
vcpu.request_interrupt_window();
}
}
}
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
fn inject_interrupt(pic: &Arc<Mutex<devices::Pic>>, vcpu: &RunnableVcpu) {}
fn run_vcpu(
vcpu: Vcpu,
cpu_id: u32,
@ -1480,6 +1500,11 @@ fn run_vcpu(
run_mode_lock = run_mode_arc.cvar.wait(run_mode_lock);
}
}
if cpu_id != 0 { continue; }
if let Some((pic, _)) = &split_irqchip {
inject_interrupt(pic, &vcpu);
}
}
}
})