blob: bfab4ed7c2717ada8ce3b6f129a7a146c405790a [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
use std::fmt::{self, Display};
use super::IrqEvent;
use crate::bus::BusAccessInfo;
use crate::BusDevice;
use base::{error, warn, AsRawDescriptor, Error, Event, Result};
use hypervisor::{IoapicState, MsiAddressMessage, MsiDataMessage, TriggerMode, NUM_IOAPIC_PINS};
use msg_socket::{MsgError, MsgReceiver, MsgSender};
use vm_control::{MaybeOwnedDescriptor, VmIrqRequest, VmIrqRequestSocket, VmIrqResponse};
const IOAPIC_VERSION_ID: u32 = 0x00170011;
pub const IOAPIC_BASE_ADDRESS: u64 = 0xfec00000;
// The Intel manual does not specify this size, but KVM uses it.
pub const IOAPIC_MEM_LENGTH_BYTES: u64 = 0x100;
// Constants for IOAPIC direct register offset.
const IOAPIC_REG_ID: u8 = 0x00;
const IOAPIC_REG_VERSION: u8 = 0x01;
const IOAPIC_REG_ARBITRATION_ID: u8 = 0x02;
// Register offsets
const IOREGSEL_OFF: u8 = 0x0;
const IOREGSEL_DUMMY_UPPER_32_BITS_OFF: u8 = 0x4;
const IOWIN_OFF: u8 = 0x10;
const IOWIN_SCALE: u8 = 0x2;
/// Given an IRQ and whether or not the selector should refer to the high bits, return a selector
/// suitable to use as an offset to read to/write from.
#[allow(dead_code)]
fn encode_selector_from_irq(irq: usize, is_high_bits: bool) -> u8 {
(irq as u8) * IOWIN_SCALE + IOWIN_OFF + (is_high_bits as u8)
}
/// Given an offset that was read from/written to, return a tuple of the relevant IRQ and whether
/// the offset refers to the high bits of that register.
fn decode_irq_from_selector(selector: u8) -> (usize, bool) {
(
((selector - IOWIN_OFF) / IOWIN_SCALE) as usize,
selector & 1 != 0,
)
}
// The RTC needs special treatment to work properly for Windows (or other OSs that use tick
// stuffing). In order to avoid time drift, we need to guarantee that the correct number of RTC
// interrupts are injected into the guest. This hack essentialy treats RTC interrupts as level
// triggered, which allows the IOAPIC to be responsible for interrupt coalescing and allows the
// IOAPIC to pass back whether or not the interrupt was coalesced to the CMOS (which allows the
// CMOS to perform tick stuffing). This deviates from the IOAPIC spec in ways very similar to (but
// not exactly the same as) KVM's IOAPIC.
const RTC_IRQ: usize = 0x8;
pub struct Ioapic {
/// State of the ioapic registers
state: IoapicState,
/// Remote IRR for Edge Triggered Real Time Clock interrupts, which allows the CMOS to know when
/// one of its interrupts is being coalesced.
rtc_remote_irr: bool,
/// Outgoing irq events that are used to inject MSI interrupts.
out_events: Vec<Option<IrqEvent>>,
/// Events that should be triggered on an EOI. The outer Vec is indexed by GSI, and the inner
/// Vec is an unordered list of registered resample events for the GSI.
resample_events: Vec<Vec<Event>>,
/// Socket used to route MSI irqs
irq_socket: VmIrqRequestSocket,
}
impl BusDevice for Ioapic {
fn debug_label(&self) -> String {
"userspace IOAPIC".to_string()
}
fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
if data.len() > 8 || data.len() == 0 {
warn!("IOAPIC: Bad read size: {}", data.len());
return;
}
if info.offset >= IOAPIC_MEM_LENGTH_BYTES {
warn!("IOAPIC: Bad read from {}", info);
}
let out = match info.offset as u8 {
IOREGSEL_OFF => self.state.ioregsel.into(),
IOREGSEL_DUMMY_UPPER_32_BITS_OFF => 0,
IOWIN_OFF => self.ioapic_read(),
_ => {
warn!("IOAPIC: Bad read from {}", info);
return;
}
};
let out_arr = out.to_ne_bytes();
for i in 0..4 {
if i < data.len() {
data[i] = out_arr[i];
}
}
}
fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
if data.len() > 8 || data.len() == 0 {
warn!("IOAPIC: Bad write size: {}", data.len());
return;
}
if info.offset >= IOAPIC_MEM_LENGTH_BYTES {
warn!("IOAPIC: Bad write to {}", info);
}
match info.offset as u8 {
IOREGSEL_OFF => self.state.ioregsel = data[0],
IOREGSEL_DUMMY_UPPER_32_BITS_OFF => {} // Ignored.
IOWIN_OFF => {
if data.len() != 4 {
warn!("IOAPIC: Bad write size for iowin: {}", data.len());
return;
}
let data_arr = [data[0], data[1], data[2], data[3]];
let val = u32::from_ne_bytes(data_arr);
self.ioapic_write(val);
}
_ => {
warn!("IOAPIC: Bad write to {}", info);
}
}
}
}
impl Ioapic {
pub fn new(irq_socket: VmIrqRequestSocket) -> Result<Ioapic> {
let mut state = IoapicState::default();
for i in 0..NUM_IOAPIC_PINS {
state.redirect_table[i].set_interrupt_mask(true);
}
Ok(Ioapic {
state,
rtc_remote_irr: false,
out_events: (0..NUM_IOAPIC_PINS).map(|_| None).collect(),
resample_events: Vec::new(),
irq_socket,
})
}
pub fn get_ioapic_state(&self) -> IoapicState {
self.state
}
pub fn set_ioapic_state(&mut self, state: &IoapicState) {
self.state = *state
}
pub fn register_resample_events(&mut self, resample_events: Vec<Vec<Event>>) {
self.resample_events = resample_events;
}
// The ioapic must be informed about EOIs in order to avoid sending multiple interrupts of the
// same type at the same time.
pub fn end_of_interrupt(&mut self, vector: u8) {
if self.state.redirect_table[RTC_IRQ].get_vector() == vector && self.rtc_remote_irr {
// Specifically clear RTC IRQ field
self.rtc_remote_irr = false;
}
for i in 0..NUM_IOAPIC_PINS {
if self.state.redirect_table[i].get_vector() == vector
&& self.state.redirect_table[i].get_trigger_mode() == TriggerMode::Level
{
if self
.resample_events
.get(i)
.map_or(false, |events| events.len() > 0)
{
self.service_irq(i, false);
}
if let Some(resample_events) = self.resample_events.get(i) {
for resample_evt in resample_events {
resample_evt.write(1).unwrap();
}
}
self.state.redirect_table[i].set_remote_irr(false);
}
// There is an inherent race condition in hardware if the OS is finished processing an
// interrupt and a new interrupt is delivered between issuing an EOI and the EOI being
// completed. When that happens the ioapic is supposed to re-inject the interrupt.
if self.state.current_interrupt_level_bitmap & (1 << i) != 0 {
self.service_irq(i, true);
}
}
}
pub fn service_irq(&mut self, irq: usize, level: bool) -> bool {
let entry = &mut self.state.redirect_table[irq];
// De-assert the interrupt.
if !level {
self.state.current_interrupt_level_bitmap &= !(1 << irq);
return true;
}
// If it's an edge-triggered interrupt that's already high we ignore it.
if entry.get_trigger_mode() == TriggerMode::Edge
&& self.state.current_interrupt_level_bitmap & (1 << irq) != 0
{
return false;
}
self.state.current_interrupt_level_bitmap |= 1 << irq;
// Interrupts are masked, so don't inject.
if entry.get_interrupt_mask() {
return false;
}
// Level-triggered and remote irr is already active, so we don't inject a new interrupt.
// (Coalesce with the prior one(s)).
if entry.get_trigger_mode() == TriggerMode::Level && entry.get_remote_irr() {
return false;
}
// Coalesce RTC interrupt to make tick stuffing work.
if irq == RTC_IRQ && self.rtc_remote_irr {
return false;
}
let injected = match self.out_events.get(irq) {
Some(Some(evt)) => evt.event.write(1).is_ok(),
_ => false,
};
if entry.get_trigger_mode() == TriggerMode::Level && level && injected {
entry.set_remote_irr(true);
} else if irq == RTC_IRQ && injected {
self.rtc_remote_irr = true;
}
injected
}
fn ioapic_write(&mut self, val: u32) {
match self.state.ioregsel {
IOAPIC_REG_VERSION => { /* read-only register */ }
IOAPIC_REG_ID => self.state.ioapicid = (val >> 24) & 0xf,
IOAPIC_REG_ARBITRATION_ID => { /* read-only register */ }
_ => {
if self.state.ioregsel < IOWIN_OFF {
// Invalid write; ignore.
return;
}
let (index, is_high_bits) = decode_irq_from_selector(self.state.ioregsel);
if index >= hypervisor::NUM_IOAPIC_PINS {
// Invalid write; ignore.
return;
}
let entry = &mut self.state.redirect_table[index];
if is_high_bits {
entry.set(32, 32, val.into());
} else {
let before = *entry;
entry.set(0, 32, val.into());
// respect R/O bits.
entry.set_delivery_status(before.get_delivery_status());
entry.set_remote_irr(before.get_remote_irr());
// Clear remote_irr when switching to edge_triggered.
if entry.get_trigger_mode() == TriggerMode::Edge {
entry.set_remote_irr(false);
}
// NOTE: on pre-4.0 kernels, there's a race we would need to work around.
// "KVM: x86: ioapic: Fix level-triggered EOI and IOAPIC reconfigure race"
// is the fix for this.
}
if self.state.redirect_table[index].get_trigger_mode() == TriggerMode::Level
&& self.state.current_interrupt_level_bitmap & (1 << index) != 0
&& !self.state.redirect_table[index].get_interrupt_mask()
{
self.service_irq(index, true);
}
let mut address = MsiAddressMessage::new();
let mut data = MsiDataMessage::new();
let entry = &self.state.redirect_table[index];
address.set_destination_mode(entry.get_dest_mode());
address.set_destination_id(entry.get_dest_id());
address.set_always_0xfee(0xfee);
data.set_vector(entry.get_vector());
data.set_delivery_mode(entry.get_delivery_mode());
data.set_trigger(entry.get_trigger_mode());
let msi_address = address.get(0, 32);
let msi_data = data.get(0, 32);
if let Err(e) = self.setup_msi(index, msi_address, msi_data as u32) {
error!("IOAPIC failed to set up MSI for index {}: {}", index, e);
}
}
}
}
fn setup_msi(
&mut self,
index: usize,
msi_address: u64,
msi_data: u32,
) -> std::result::Result<(), IoapicError> {
if msi_data == 0 {
// During boot, Linux first configures all ioapic pins with msi_data == 0; the routes
// aren't yet assigned to devices and aren't usable. We skip MSI setup if msi_data is
// 0.
return Ok(());
}
// Allocate a GSI and event for the outgoing route, if we haven't already done it.
// The event will be used on the "outgoing" end of the ioapic to send an interrupt to the
// apics: when an incoming ioapic irq line gets signalled, the ioapic writes to the
// corresponding outgoing event. The GSI number is used to update the routing info (MSI
// data and addr) for the event. The GSI and event are allocated only once for each ioapic
// irq line, when the guest first sets up the ioapic with a valid route. If the guest
// later reconfigures an ioapic irq line, the same GSI and event are reused, and we change
// the GSI's route to the new MSI data+addr destination.
let gsi = if let Some(evt) = &self.out_events[index] {
evt.gsi
} else {
let event = Event::new().map_err(|e| IoapicError::CreateEvent(e))?;
let request = VmIrqRequest::AllocateOneMsi {
irqfd: MaybeOwnedDescriptor::Borrowed(event.as_raw_descriptor()),
};
self.irq_socket
.send(&request)
.map_err(IoapicError::AllocateOneMsiSend)?;
match self
.irq_socket
.recv()
.map_err(IoapicError::AllocateOneMsiRecv)?
{
VmIrqResponse::AllocateOneMsi { gsi, .. } => {
self.out_events[index] = Some(IrqEvent {
gsi,
event,
resample_event: None,
});
gsi
}
VmIrqResponse::Err(e) => return Err(IoapicError::AllocateOneMsi(e)),
_ => unreachable!(),
}
};
// Set the MSI route for the GSI. This controls which apic(s) get the interrupt when the
// ioapic's outgoing event is written, and various attributes of how the interrupt is
// delivered.
let request = VmIrqRequest::AddMsiRoute {
gsi,
msi_address,
msi_data,
};
self.irq_socket
.send(&request)
.map_err(IoapicError::AddMsiRouteSend)?;
if let VmIrqResponse::Err(e) = self
.irq_socket
.recv()
.map_err(IoapicError::AddMsiRouteRecv)?
{
return Err(IoapicError::AddMsiRoute(e));
}
Ok(())
}
fn ioapic_read(&mut self) -> u32 {
match self.state.ioregsel {
IOAPIC_REG_VERSION => IOAPIC_VERSION_ID,
IOAPIC_REG_ID | IOAPIC_REG_ARBITRATION_ID => (self.state.ioapicid & 0xf) << 24,
_ => {
if self.state.ioregsel < IOWIN_OFF {
// Invalid read; ignore and return 0.
0
} else {
let (index, is_high_bits) = decode_irq_from_selector(self.state.ioregsel);
if index < NUM_IOAPIC_PINS {
let offset = if is_high_bits { 32 } else { 0 };
self.state.redirect_table[index].get(offset, 32) as u32
} else {
!0 // Invalid index - return all 1s
}
}
}
}
}
}
#[derive(Debug)]
enum IoapicError {
AddMsiRoute(Error),
AddMsiRouteRecv(MsgError),
AddMsiRouteSend(MsgError),
AllocateOneMsi(Error),
AllocateOneMsiRecv(MsgError),
AllocateOneMsiSend(MsgError),
CreateEvent(Error),
}
impl Display for IoapicError {
#[remain::check]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::IoapicError::*;
#[sorted]
match self {
AddMsiRoute(e) => write!(f, "AddMsiRoute failed: {}", e),
AddMsiRouteRecv(e) => write!(f, "failed to receive AddMsiRoute response: {}", e),
AddMsiRouteSend(e) => write!(f, "failed to send AddMsiRoute request: {}", e),
AllocateOneMsi(e) => write!(f, "AllocateOneMsi failed: {}", e),
AllocateOneMsiRecv(e) => write!(f, "failed to receive AllocateOneMsi response: {}", e),
AllocateOneMsiSend(e) => write!(f, "failed to send AllocateOneMsi request: {}", e),
CreateEvent(e) => write!(f, "failed to create event object: {}", e),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use hypervisor::{DeliveryMode, DeliveryStatus, DestinationMode, IoapicRedirectionTableEntry};
const DEFAULT_VECTOR: u8 = 0x3a;
const DEFAULT_DESTINATION_ID: u8 = 0x5f;
fn new() -> Ioapic {
let (_, device_socket) = msg_socket::pair::<VmIrqResponse, VmIrqRequest>().unwrap();
Ioapic::new(device_socket).unwrap()
}
fn ioapic_bus_address(offset: u8) -> BusAccessInfo {
let offset = offset as u64;
BusAccessInfo {
offset,
address: IOAPIC_BASE_ADDRESS + offset,
id: 0,
}
}
fn set_up(trigger: TriggerMode) -> (Ioapic, usize) {
let irq = NUM_IOAPIC_PINS - 1;
let ioapic = set_up_with_irq(irq, trigger);
(ioapic, irq)
}
fn set_up_with_irq(irq: usize, trigger: TriggerMode) -> Ioapic {
let mut ioapic = self::new();
set_up_redirection_table_entry(&mut ioapic, irq, trigger);
ioapic.out_events[irq] = Some(IrqEvent {
gsi: NUM_IOAPIC_PINS as u32,
event: Event::new().unwrap(),
resample_event: None,
});
ioapic
}
fn read_reg(ioapic: &mut Ioapic, selector: u8) -> u32 {
let mut data = [0; 4];
ioapic.write(ioapic_bus_address(IOREGSEL_OFF), &[selector]);
ioapic.read(ioapic_bus_address(IOWIN_OFF), &mut data);
u32::from_ne_bytes(data)
}
fn write_reg(ioapic: &mut Ioapic, selector: u8, value: u32) {
ioapic.write(ioapic_bus_address(IOREGSEL_OFF), &[selector]);
ioapic.write(ioapic_bus_address(IOWIN_OFF), &value.to_ne_bytes());
}
fn read_entry(ioapic: &mut Ioapic, irq: usize) -> IoapicRedirectionTableEntry {
let mut entry = IoapicRedirectionTableEntry::new();
entry.set(
0,
32,
read_reg(ioapic, encode_selector_from_irq(irq, false)).into(),
);
entry.set(
32,
32,
read_reg(ioapic, encode_selector_from_irq(irq, true)).into(),
);
entry
}
fn write_entry(ioapic: &mut Ioapic, irq: usize, entry: IoapicRedirectionTableEntry) {
write_reg(
ioapic,
encode_selector_from_irq(irq, false),
entry.get(0, 32) as u32,
);
write_reg(
ioapic,
encode_selector_from_irq(irq, true),
entry.get(32, 32) as u32,
);
}
fn set_up_redirection_table_entry(ioapic: &mut Ioapic, irq: usize, trigger_mode: TriggerMode) {
let mut entry = IoapicRedirectionTableEntry::new();
entry.set_vector(DEFAULT_DESTINATION_ID);
entry.set_delivery_mode(DeliveryMode::Startup);
entry.set_delivery_status(DeliveryStatus::Pending);
entry.set_dest_id(DEFAULT_VECTOR);
entry.set_trigger_mode(trigger_mode);
write_entry(ioapic, irq, entry);
}
fn set_mask(ioapic: &mut Ioapic, irq: usize, mask: bool) {
let mut entry = read_entry(ioapic, irq);
entry.set_interrupt_mask(mask);
write_entry(ioapic, irq, entry);
}
#[test]
fn write_read_ioregsel() {
let mut ioapic = self::new();
let data_write = [0x0f, 0xf0, 0x01, 0xff];
let mut data_read = [0; 4];
for i in 0..data_write.len() {
ioapic.write(ioapic_bus_address(IOREGSEL_OFF), &data_write[i..i + 1]);
ioapic.read(ioapic_bus_address(IOREGSEL_OFF), &mut data_read[i..i + 1]);
assert_eq!(data_write[i], data_read[i]);
}
}
// Verify that version register is actually read-only.
#[test]
fn write_read_ioaic_reg_version() {
let mut ioapic = self::new();
let before = read_reg(&mut ioapic, IOAPIC_REG_VERSION);
let data_write = !before;
write_reg(&mut ioapic, IOAPIC_REG_VERSION, data_write);
assert_eq!(read_reg(&mut ioapic, IOAPIC_REG_VERSION), before);
}
// Verify that only bits 27:24 of the IOAPICID are readable/writable.
#[test]
fn write_read_ioapic_reg_id() {
let mut ioapic = self::new();
write_reg(&mut ioapic, IOAPIC_REG_ID, 0x1f3e5d7c);
assert_eq!(read_reg(&mut ioapic, IOAPIC_REG_ID), 0x0f000000);
}
// Write to read-only register IOAPICARB.
#[test]
fn write_read_ioapic_arbitration_id() {
let mut ioapic = self::new();
let data_write_id = 0x1f3e5d7c;
let expected_result = 0x0f000000;
// Write to IOAPICID. This should also change IOAPICARB.
write_reg(&mut ioapic, IOAPIC_REG_ID, data_write_id);
// Read IOAPICARB
assert_eq!(
read_reg(&mut ioapic, IOAPIC_REG_ARBITRATION_ID),
expected_result
);
// Try to write to IOAPICARB and verify unchanged result.
write_reg(&mut ioapic, IOAPIC_REG_ARBITRATION_ID, !data_write_id);
assert_eq!(
read_reg(&mut ioapic, IOAPIC_REG_ARBITRATION_ID),
expected_result
);
}
#[test]
#[should_panic(expected = "index out of bounds: the len is 24 but the index is 24")]
fn service_invalid_irq() {
let mut ioapic = self::new();
ioapic.service_irq(NUM_IOAPIC_PINS, false);
}
// Test a level triggered IRQ interrupt.
#[test]
fn service_level_irq() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
// TODO(mutexlox): Check that interrupt is fired once.
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
}
#[test]
fn service_multiple_level_irqs() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
// TODO(mutexlox): Check that interrupt is fired twice.
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
ioapic.end_of_interrupt(DEFAULT_DESTINATION_ID);
ioapic.service_irq(irq, true);
}
// Test multiple level interrupts without an EOI and verify that only one interrupt is
// delivered.
#[test]
fn coalesce_multiple_level_irqs() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
// TODO(mutexlox): Test that only one interrupt is delivered.
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
ioapic.service_irq(irq, true);
}
// Test multiple RTC interrupts without an EOI and verify that only one interrupt is delivered.
#[test]
fn coalesce_multiple_rtc_irqs() {
let irq = RTC_IRQ;
let mut ioapic = set_up_with_irq(irq, TriggerMode::Edge);
// TODO(mutexlox): Verify that only one IRQ is delivered.
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
ioapic.service_irq(irq, true);
}
// Test that a level interrupt that has been coalesced is re-raised if a guest issues an
// EndOfInterrupt after the interrupt was coalesced while the line is still asserted.
#[test]
fn reinject_level_interrupt() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
// TODO(mutexlox): Verify that only one IRQ is delivered.
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
ioapic.service_irq(irq, true);
// TODO(mutexlox): Verify that this last interrupt occurs as a result of the EOI, rather
// than in response to the last service_irq.
ioapic.end_of_interrupt(DEFAULT_DESTINATION_ID);
}
#[test]
fn service_edge_triggered_irq() {
let (mut ioapic, irq) = set_up(TriggerMode::Edge);
// TODO(mutexlox): Verify that one interrupt is delivered.
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, true); // Repeated asserts before a deassert should be ignored.
ioapic.service_irq(irq, false);
}
// Verify that the state of an edge-triggered interrupt is properly tracked even when the
// interrupt is disabled.
#[test]
fn edge_trigger_unmask_test() {
let (mut ioapic, irq) = set_up(TriggerMode::Edge);
// TODO(mutexlox): Expect an IRQ.
ioapic.service_irq(irq, true);
set_mask(&mut ioapic, irq, true);
ioapic.service_irq(irq, false);
// No interrupt triggered while masked.
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
set_mask(&mut ioapic, irq, false);
// TODO(mutexlox): Expect another interrupt.
// Interrupt triggered while unmasked, even though when it was masked the level was high.
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
}
// Verify that a level-triggered interrupt that is triggered while masked will fire once the
// interrupt is unmasked.
#[test]
fn level_trigger_unmask_test() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
set_mask(&mut ioapic, irq, true);
ioapic.service_irq(irq, true);
// TODO(mutexlox): expect an interrupt after this.
set_mask(&mut ioapic, irq, false);
}
// Verify that multiple asserts before a deassert are ignored even if there's an EOI between
// them.
#[test]
fn end_of_interrupt_edge_triggered_irq() {
let (mut ioapic, irq) = set_up(TriggerMode::Edge);
// TODO(mutexlox): Expect 1 interrupt.
ioapic.service_irq(irq, true);
ioapic.end_of_interrupt(DEFAULT_DESTINATION_ID);
// Repeated asserts before a de-assert should be ignored.
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
}
// Send multiple edge-triggered interrupts in a row.
#[test]
fn service_multiple_edge_irqs() {
let (mut ioapic, irq) = set_up(TriggerMode::Edge);
ioapic.service_irq(irq, true);
// TODO(mutexlox): Verify that an interrupt occurs here.
ioapic.service_irq(irq, false);
ioapic.service_irq(irq, true);
// TODO(mutexlox): Verify that an interrupt occurs here.
ioapic.service_irq(irq, false);
}
// Test an interrupt line with negative polarity.
#[test]
fn service_negative_polarity_irq() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
let mut entry = read_entry(&mut ioapic, irq);
entry.set_polarity(1);
write_entry(&mut ioapic, irq, entry);
// TODO(mutexlox): Expect an interrupt to fire.
ioapic.service_irq(irq, false);
}
// Ensure that remote IRR can't be edited via mmio.
#[test]
fn remote_irr_read_only() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
ioapic.state.redirect_table[irq].set_remote_irr(true);
let mut entry = read_entry(&mut ioapic, irq);
entry.set_remote_irr(false);
write_entry(&mut ioapic, irq, entry);
assert_eq!(read_entry(&mut ioapic, irq).get_remote_irr(), true);
}
#[test]
fn delivery_status_read_only() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
ioapic.state.redirect_table[irq].set_delivery_status(DeliveryStatus::Pending);
let mut entry = read_entry(&mut ioapic, irq);
entry.set_delivery_status(DeliveryStatus::Idle);
write_entry(&mut ioapic, irq, entry);
assert_eq!(
read_entry(&mut ioapic, irq).get_delivery_status(),
DeliveryStatus::Pending
);
}
#[test]
fn level_to_edge_transition_clears_remote_irr() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
ioapic.state.redirect_table[irq].set_remote_irr(true);
let mut entry = read_entry(&mut ioapic, irq);
entry.set_trigger_mode(TriggerMode::Edge);
write_entry(&mut ioapic, irq, entry);
assert_eq!(read_entry(&mut ioapic, irq).get_remote_irr(), false);
}
#[test]
fn masking_preserves_remote_irr() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
ioapic.state.redirect_table[irq].set_remote_irr(true);
set_mask(&mut ioapic, irq, true);
set_mask(&mut ioapic, irq, false);
assert_eq!(read_entry(&mut ioapic, irq).get_remote_irr(), true);
}
// Test reconfiguration racing with EOIs.
#[test]
fn reconfiguration_race() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
// Fire one level-triggered interrupt.
// TODO(mutexlox): Check that it fires.
ioapic.service_irq(irq, true);
// Read the redirection table entry before the EOI...
let mut entry = read_entry(&mut ioapic, irq);
entry.set_trigger_mode(TriggerMode::Edge);
ioapic.service_irq(irq, false);
ioapic.end_of_interrupt(DEFAULT_DESTINATION_ID);
// ... and write back that (modified) value.
write_entry(&mut ioapic, irq, entry);
// Fire one *edge* triggered interrupt
// TODO(mutexlox): Assert that the interrupt fires once.
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
}
// Ensure that swapping to edge triggered and back clears the remote irr bit.
#[test]
fn implicit_eoi() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
// Fire one level-triggered interrupt.
ioapic.service_irq(irq, true);
// TODO(mutexlox): Verify that one interrupt was fired.
ioapic.service_irq(irq, false);
// Do an implicit EOI by cycling between edge and level triggered.
let mut entry = read_entry(&mut ioapic, irq);
entry.set_trigger_mode(TriggerMode::Edge);
write_entry(&mut ioapic, irq, entry);
entry.set_trigger_mode(TriggerMode::Level);
write_entry(&mut ioapic, irq, entry);
// Fire one level-triggered interrupt.
ioapic.service_irq(irq, true);
// TODO(mutexlox): Verify that one interrupt fires.
ioapic.service_irq(irq, false);
}
#[test]
fn set_redirection_entry_by_bits() {
let mut entry = IoapicRedirectionTableEntry::new();
// destination_mode
// polarity |
// trigger_mode | |
// | | |
// 0011 1010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1001 0110 0101 1111
// |_______| |______________________________________________|| | | |_| |_______|
// dest_id reserved | | | | vector
// interrupt_mask | | |
// remote_irr | |
// delivery_status |
// delivery_mode
entry.set(0, 64, 0x3a0000000000965f);
assert_eq!(entry.get_vector(), 0x5f);
assert_eq!(entry.get_delivery_mode(), DeliveryMode::Startup);
assert_eq!(entry.get_dest_mode(), DestinationMode::Physical);
assert_eq!(entry.get_delivery_status(), DeliveryStatus::Pending);
assert_eq!(entry.get_polarity(), 0);
assert_eq!(entry.get_remote_irr(), false);
assert_eq!(entry.get_trigger_mode(), TriggerMode::Level);
assert_eq!(entry.get_interrupt_mask(), false);
assert_eq!(entry.get_reserved(), 0);
assert_eq!(entry.get_dest_id(), 0x3a);
let (mut ioapic, irq) = set_up(TriggerMode::Edge);
write_entry(&mut ioapic, irq, entry);
assert_eq!(
read_entry(&mut ioapic, irq).get_trigger_mode(),
TriggerMode::Level
);
// TODO(mutexlox): Verify that this actually fires an interrupt.
ioapic.service_irq(irq, true);
}
}