use anyhow::Context;
use base::error;
use base::warn;
use base::Error;
use base::Event;
use base::Result;
use base::Tube;
use base::TubeError;
use hypervisor::IoapicRedirectionTableEntry;
use hypervisor::IoapicState;
use hypervisor::MsiAddressMessage;
use hypervisor::MsiDataMessage;
use hypervisor::TriggerMode;
use hypervisor::NUM_IOAPIC_PINS;
use remain::sorted;
use serde::Deserialize;
use serde::Serialize;
use snapshot::AnySnapshot;
use thiserror::Error;
use vm_control::DeviceId;
use vm_control::PlatformDeviceId;
use vm_control::VmIrqRequest;
use vm_control::VmIrqResponse;
use super::IrqEvent;
use crate::bus::BusAccessInfo;
use crate::BusDevice;
use crate::IrqEventSource;
use crate::Suspendable;
const IOAPIC_VERSION_ID: u32 = 0x00000020;
pub const IOAPIC_BASE_ADDRESS: u64 = 0xfec00000;
pub const IOAPIC_MEM_LENGTH_BYTES: u64 = 0x100;
const IOAPIC_REG_ID: u8 = 0x00;
const IOAPIC_REG_VERSION: u8 = 0x01;
const IOAPIC_REG_ARBITRATION_ID: u8 = 0x02;
const IOREGSEL_OFF: u8 = 0x0;
const IOREGSEL_DUMMY_UPPER_32_BITS_OFF: u8 = 0x4;
const IOWIN_OFF: u8 = 0x10;
const IOEOIR_OFF: u8 = 0x40;
const IOWIN_SCALE: u8 = 0x2;
#[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)
}
fn decode_irq_from_selector(selector: u8) -> (usize, bool) {
(
((selector - IOWIN_OFF) / IOWIN_SCALE) as usize,
selector & 1 != 0,
)
}
const RTC_IRQ: usize = 0x8;
#[derive(Clone, Serialize, Deserialize)]
struct OutEventSnapshot {
gsi: u32,
msi_address: u64,
msi_data: u32,
source: IrqEventSource,
}
#[derive(Serialize, Deserialize)]
struct IoapicSnapshot {
num_pins: usize,
ioregsel: u8,
ioapicid: u32,
rtc_remote_irr: bool,
out_event_snapshots: Vec<Option<OutEventSnapshot>>,
redirect_table: Vec<IoapicRedirectionTableEntry>,
interrupt_level: Vec<bool>,
}
struct OutEvent {
irq_event: IrqEvent,
snapshot: Option<OutEventSnapshot>,
}
pub struct Ioapic {
num_pins: usize,
ioregsel: u8,
ioapicid: u32,
rtc_remote_irr: bool,
out_events: Vec<Option<OutEvent>>,
resample_events: Vec<Vec<Event>>,
redirect_table: Vec<IoapicRedirectionTableEntry>,
interrupt_level: Vec<bool>,
irq_tube: Tube,
}
impl BusDevice for Ioapic {
fn debug_label(&self) -> String {
"userspace IOAPIC".to_string()
}
fn device_id(&self) -> DeviceId {
PlatformDeviceId::Ioapic.into()
}
fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
if data.len() > 8 || data.is_empty() {
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.ioregsel.into(),
IOREGSEL_DUMMY_UPPER_32_BITS_OFF => 0,
IOWIN_OFF => self.ioapic_read(),
IOEOIR_OFF => 0,
_ => {
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.is_empty() {
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.ioregsel = data[0],
IOREGSEL_DUMMY_UPPER_32_BITS_OFF => {}
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);
}
IOEOIR_OFF => self.end_of_interrupt(data[0]),
_ => {
warn!("IOAPIC: Bad write to {}", info);
}
}
}
}
impl Ioapic {
pub fn new(irq_tube: Tube, num_pins: usize) -> Result<Ioapic> {
assert_eq!(num_pins, NUM_IOAPIC_PINS);
let mut entry = IoapicRedirectionTableEntry::new();
entry.set_interrupt_mask(true);
Ok(Ioapic {
num_pins,
ioregsel: 0,
ioapicid: 0,
rtc_remote_irr: false,
out_events: (0..num_pins).map(|_| None).collect(),
resample_events: Vec::new(),
redirect_table: (0..num_pins).map(|_| entry).collect(),
interrupt_level: (0..num_pins).map(|_| false).collect(),
irq_tube,
})
}
pub fn get_ioapic_state(&self) -> IoapicState {
let level_bitmap = self
.interrupt_level
.iter()
.take(NUM_IOAPIC_PINS)
.rev()
.fold(0, |acc, &l| acc * 2 + l as u32);
let mut state = IoapicState {
base_address: IOAPIC_BASE_ADDRESS,
ioregsel: self.ioregsel,
ioapicid: self.ioapicid,
current_interrupt_level_bitmap: level_bitmap,
..Default::default()
};
for (dst, src) in state
.redirect_table
.iter_mut()
.zip(self.redirect_table.iter())
{
*dst = *src;
}
state
}
pub fn set_ioapic_state(&mut self, state: &IoapicState) {
self.ioregsel = state.ioregsel;
self.ioapicid = state.ioapicid & 0x0f00_0000;
for (src, dst) in state
.redirect_table
.iter()
.zip(self.redirect_table.iter_mut())
{
*dst = *src;
}
for (i, level) in self
.interrupt_level
.iter_mut()
.take(NUM_IOAPIC_PINS)
.enumerate()
{
*level = state.current_interrupt_level_bitmap & (1 << i) != 0;
}
}
pub fn register_resample_events(&mut self, resample_events: Vec<Vec<Event>>) {
self.resample_events = resample_events;
}
pub fn end_of_interrupt(&mut self, vector: u8) {
if self.redirect_table[RTC_IRQ].get_vector() == vector && self.rtc_remote_irr {
self.rtc_remote_irr = false;
}
for i in 0..self.num_pins {
if self.redirect_table[i].get_vector() == vector
&& self.redirect_table[i].get_trigger_mode() == TriggerMode::Level
{
if self
.resample_events
.get(i)
.is_some_and(|events| !events.is_empty())
{
self.service_irq(i, false);
}
if let Some(resample_events) = self.resample_events.get(i) {
for resample_evt in resample_events {
resample_evt.signal().unwrap();
}
}
self.redirect_table[i].set_remote_irr(false);
}
if self.interrupt_level[i] {
self.service_irq(i, true);
}
}
}
pub fn service_irq(&mut self, irq: usize, level: bool) -> bool {
let entry = &mut self.redirect_table[irq];
if !level {
self.interrupt_level[irq] = false;
return true;
}
if entry.get_trigger_mode() == TriggerMode::Edge && self.interrupt_level[irq] {
return false;
}
self.interrupt_level[irq] = true;
if entry.get_interrupt_mask() {
return false;
}
if entry.get_trigger_mode() == TriggerMode::Level && entry.get_remote_irr() {
return false;
}
if irq == RTC_IRQ && self.rtc_remote_irr {
return false;
}
let injected = match self.out_events.get(irq) {
Some(Some(out_event)) => out_event.irq_event.event.signal().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.ioregsel {
IOAPIC_REG_VERSION => { }
IOAPIC_REG_ID => self.ioapicid = val & 0x0f00_0000,
IOAPIC_REG_ARBITRATION_ID => { }
_ => {
if self.ioregsel < IOWIN_OFF {
return;
}
let (index, is_high_bits) = decode_irq_from_selector(self.ioregsel);
if index >= self.num_pins {
return;
}
let entry = &mut self.redirect_table[index];
if is_high_bits {
entry.set(32, 32, val.into());
} else {
let before = *entry;
entry.set(0, 32, val.into());
entry.set_delivery_status(before.get_delivery_status());
entry.set_remote_irr(before.get_remote_irr());
if entry.get_trigger_mode() == TriggerMode::Edge {
entry.set_remote_irr(false);
}
}
if self.redirect_table[index].get_trigger_mode() == TriggerMode::Level
&& self.interrupt_level[index]
&& !self.redirect_table[index].get_interrupt_mask()
{
self.service_irq(index, true);
}
let mut address = MsiAddressMessage::new();
let mut data = MsiDataMessage::new();
let entry = &self.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 {
return Ok(());
}
let name = self.debug_label();
let gsi = if let Some(evt) = &self.out_events[index] {
evt.irq_event.gsi
} else {
let event = Event::new().map_err(IoapicError::CreateEvent)?;
let request = VmIrqRequest::AllocateOneMsi {
irqfd: event,
device_id: self.device_id(),
queue_id: index,
device_name: name.clone(),
};
self.irq_tube
.send(&request)
.map_err(IoapicError::AllocateOneMsiSend)?;
match self
.irq_tube
.recv()
.map_err(IoapicError::AllocateOneMsiRecv)?
{
VmIrqResponse::AllocateOneMsi { gsi, .. } => {
self.out_events[index] = Some(OutEvent {
irq_event: IrqEvent {
gsi,
event: match request {
VmIrqRequest::AllocateOneMsi { irqfd, .. } => irqfd,
_ => unreachable!(),
},
resample_event: None,
source: IrqEventSource {
device_id: self.device_id(),
queue_id: index,
device_name: name,
},
},
snapshot: None,
});
gsi
}
VmIrqResponse::Err(e) => return Err(IoapicError::AllocateOneMsi(e)),
_ => unreachable!(),
}
};
let request = VmIrqRequest::AddMsiRoute {
gsi,
msi_address,
msi_data,
};
self.irq_tube
.send(&request)
.map_err(IoapicError::AddMsiRouteSend)?;
if let VmIrqResponse::Err(e) = self.irq_tube.recv().map_err(IoapicError::AddMsiRouteRecv)? {
return Err(IoapicError::AddMsiRoute(e));
}
self.out_events[index]
.as_mut()
.expect("IRQ is guaranteed initialized")
.snapshot = Some(OutEventSnapshot {
gsi,
msi_address,
msi_data,
source: IrqEventSource {
device_id: self.device_id(),
queue_id: index,
device_name: self.debug_label(),
},
});
Ok(())
}
fn restore_msi(
&mut self,
index: usize,
gsi: u32,
msi_address: u64,
msi_data: u32,
) -> std::result::Result<(), IoapicError> {
let event = Event::new().map_err(IoapicError::CreateEvent)?;
let name = self.debug_label();
let request = VmIrqRequest::AllocateOneMsiAtGsi {
irqfd: event,
gsi,
device_id: self.device_id(),
queue_id: index,
device_name: name.clone(),
};
self.irq_tube
.send(&request)
.map_err(IoapicError::AllocateOneMsiSend)?;
if let VmIrqResponse::Err(e) = self
.irq_tube
.recv()
.map_err(IoapicError::AllocateOneMsiRecv)?
{
return Err(IoapicError::AllocateOneMsi(e));
}
self.out_events[index] = Some(OutEvent {
irq_event: IrqEvent {
gsi,
event: match request {
VmIrqRequest::AllocateOneMsiAtGsi { irqfd, .. } => irqfd,
_ => unreachable!(),
},
resample_event: None,
source: IrqEventSource {
device_id: self.device_id(),
queue_id: index,
device_name: name,
},
},
snapshot: None,
});
let request = VmIrqRequest::AddMsiRoute {
gsi,
msi_address,
msi_data,
};
self.irq_tube
.send(&request)
.map_err(IoapicError::AddMsiRouteSend)?;
if let VmIrqResponse::Err(e) = self.irq_tube.recv().map_err(IoapicError::AddMsiRouteRecv)? {
return Err(IoapicError::AddMsiRoute(e));
}
self.out_events[index]
.as_mut()
.expect("IRQ is guaranteed initialized")
.snapshot = Some(OutEventSnapshot {
gsi,
msi_address,
msi_data,
source: IrqEventSource {
device_id: self.device_id(),
queue_id: index,
device_name: self.debug_label(),
},
});
Ok(())
}
fn release_all_msis(&mut self) -> std::result::Result<(), IoapicError> {
for out_event in self.out_events.drain(..).flatten() {
let request = VmIrqRequest::ReleaseOneIrq {
gsi: out_event.irq_event.gsi,
irqfd: out_event.irq_event.event,
};
self.irq_tube
.send(&request)
.map_err(IoapicError::ReleaseOneIrqSend)?;
if let VmIrqResponse::Err(e) = self
.irq_tube
.recv()
.map_err(IoapicError::ReleaseOneIrqRecv)?
{
return Err(IoapicError::ReleaseOneIrq(e));
}
}
Ok(())
}
fn ioapic_read(&mut self) -> u32 {
match self.ioregsel {
IOAPIC_REG_VERSION => ((self.num_pins - 1) as u32) << 16 | IOAPIC_VERSION_ID,
IOAPIC_REG_ID | IOAPIC_REG_ARBITRATION_ID => self.ioapicid,
_ => {
if self.ioregsel < IOWIN_OFF {
0
} else {
let (index, is_high_bits) = decode_irq_from_selector(self.ioregsel);
if index < self.num_pins {
let offset = if is_high_bits { 32 } else { 0 };
self.redirect_table[index].get(offset, 32) as u32
} else {
!0
}
}
}
}
}
}
impl Suspendable for Ioapic {
fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
AnySnapshot::to_any(IoapicSnapshot {
num_pins: self.num_pins,
ioregsel: self.ioregsel,
ioapicid: self.ioapicid,
rtc_remote_irr: self.rtc_remote_irr,
out_event_snapshots: self
.out_events
.iter()
.map(|out_event_opt| {
if let Some(out_event) = out_event_opt {
out_event.snapshot.clone()
} else {
None
}
})
.collect(),
redirect_table: self.redirect_table.clone(),
interrupt_level: self.interrupt_level.clone(),
})
.context("failed serializing Ioapic")
}
fn restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
let snap: IoapicSnapshot =
AnySnapshot::from_any(data).context("failed to deserialize Ioapic snapshot")?;
self.num_pins = snap.num_pins;
self.ioregsel = snap.ioregsel;
self.ioapicid = snap.ioapicid;
self.rtc_remote_irr = snap.rtc_remote_irr;
self.redirect_table = snap.redirect_table;
self.interrupt_level = snap.interrupt_level;
self.release_all_msis()
.context("failed to clear MSIs prior to restore")?;
self.out_events = (0..snap.num_pins).map(|_| None).collect();
for (index, maybe_out_event) in snap.out_event_snapshots.iter().enumerate() {
if let Some(out_event) = maybe_out_event {
self.restore_msi(
index,
out_event.gsi,
out_event.msi_address,
out_event.msi_data,
)?;
}
}
Ok(())
}
fn sleep(&mut self) -> anyhow::Result<()> {
Ok(())
}
fn wake(&mut self) -> anyhow::Result<()> {
Ok(())
}
}
#[sorted]
#[derive(Error, Debug)]
enum IoapicError {
#[error("AddMsiRoute failed: {0}")]
AddMsiRoute(Error),
#[error("failed to receive AddMsiRoute response: {0}")]
AddMsiRouteRecv(TubeError),
#[error("failed to send AddMsiRoute request: {0}")]
AddMsiRouteSend(TubeError),
#[error("AllocateOneMsi failed: {0}")]
AllocateOneMsi(Error),
#[error("failed to receive AllocateOneMsi response: {0}")]
AllocateOneMsiRecv(TubeError),
#[error("failed to send AllocateOneMsi request: {0}")]
AllocateOneMsiSend(TubeError),
#[error("failed to create event object: {0}")]
CreateEvent(Error),
#[error("ReleaseOneIrq failed: {0}")]
ReleaseOneIrq(Error),
#[error("failed to receive ReleaseOneIrq response: {0}")]
ReleaseOneIrqRecv(TubeError),
#[error("failed to send ReleaseOneIrq request: {0}")]
ReleaseOneIrqSend(TubeError),
}
#[cfg(test)]
mod tests {
use std::thread;
use hypervisor::DeliveryMode;
use hypervisor::DeliveryStatus;
use hypervisor::DestinationMode;
use super::*;
const DEFAULT_VECTOR: u8 = 0x3a;
const DEFAULT_DESTINATION_ID: u8 = 0x5f;
fn new() -> Ioapic {
let (_, irq_tube) = Tube::pair().unwrap();
Ioapic::new(irq_tube, NUM_IOAPIC_PINS).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(OutEvent {
irq_event: IrqEvent {
gsi: NUM_IOAPIC_PINS as u32,
event: Event::new().unwrap(),
resample_event: None,
source: IrqEventSource {
device_id: ioapic.device_id(),
queue_id: irq,
device_name: ioapic.debug_label(),
},
},
snapshot: Some(OutEventSnapshot {
gsi: NUM_IOAPIC_PINS as u32,
msi_address: 0xa,
msi_data: 0xd,
source: IrqEventSource {
device_id: ioapic.device_id(),
queue_id: irq,
device_name: ioapic.debug_label(),
},
}),
});
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]);
}
}
#[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);
}
#[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);
}
#[test]
fn write_read_ioapic_arbitration_id() {
let mut ioapic = self::new();
let data_write_id = 0x1f3e5d7c;
let expected_result = 0x0f000000;
write_reg(&mut ioapic, IOAPIC_REG_ID, data_write_id);
assert_eq!(
read_reg(&mut ioapic, IOAPIC_REG_ARBITRATION_ID),
expected_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]
fn service_level_irq() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
}
#[test]
fn service_multiple_level_irqs() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
ioapic.end_of_interrupt(DEFAULT_DESTINATION_ID);
ioapic.service_irq(irq, true);
}
#[test]
fn coalesce_multiple_level_irqs() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
ioapic.service_irq(irq, true);
}
#[test]
fn coalesce_multiple_rtc_irqs() {
let irq = RTC_IRQ;
let mut ioapic = set_up_with_irq(irq, TriggerMode::Edge);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
ioapic.service_irq(irq, true);
}
#[test]
fn reinject_level_interrupt() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
ioapic.service_irq(irq, true);
ioapic.end_of_interrupt(DEFAULT_DESTINATION_ID);
}
#[test]
fn service_edge_triggered_irq() {
let (mut ioapic, irq) = set_up(TriggerMode::Edge);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
}
#[test]
fn edge_trigger_unmask_test() {
let (mut ioapic, irq) = set_up(TriggerMode::Edge);
ioapic.service_irq(irq, true);
set_mask(&mut ioapic, irq, true);
ioapic.service_irq(irq, false);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
set_mask(&mut ioapic, irq, false);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
}
#[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);
set_mask(&mut ioapic, irq, false);
}
#[test]
fn end_of_interrupt_edge_triggered_irq() {
let (mut ioapic, irq) = set_up(TriggerMode::Edge);
ioapic.service_irq(irq, true);
ioapic.end_of_interrupt(DEFAULT_DESTINATION_ID);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
}
#[test]
fn service_multiple_edge_irqs() {
let (mut ioapic, irq) = set_up(TriggerMode::Edge);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
}
#[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);
ioapic.service_irq(irq, false);
}
#[test]
fn remote_irr_read_only() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
ioapic.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.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.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.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]
fn reconfiguration_race() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
ioapic.service_irq(irq, true);
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);
write_entry(&mut ioapic, irq, entry);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
}
#[test]
fn implicit_eoi() {
let (mut ioapic, irq) = set_up(TriggerMode::Level);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
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);
ioapic.service_irq(irq, true);
ioapic.service_irq(irq, false);
}
#[test]
fn set_redirection_entry_by_bits() {
let mut entry = IoapicRedirectionTableEntry::new();
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
);
ioapic.service_irq(irq, true);
}
#[track_caller]
fn recv_allocate_msi(t: &Tube) -> u32 {
match t.recv::<VmIrqRequest>().unwrap() {
VmIrqRequest::AllocateOneMsiAtGsi { gsi, .. } => gsi,
msg => panic!("unexpected irqchip message: {msg:?}"),
}
}
struct MsiRouteDetails {
gsi: u32,
msi_address: u64,
msi_data: u32,
}
#[track_caller]
fn recv_add_msi_route(t: &Tube) -> MsiRouteDetails {
match t.recv::<VmIrqRequest>().unwrap() {
VmIrqRequest::AddMsiRoute {
gsi,
msi_address,
msi_data,
} => MsiRouteDetails {
gsi,
msi_address,
msi_data,
},
msg => panic!("unexpected irqchip message: {msg:?}"),
}
}
#[track_caller]
fn recv_release_one_irq(t: &Tube) -> u32 {
match t.recv::<VmIrqRequest>().unwrap() {
VmIrqRequest::ReleaseOneIrq { gsi, irqfd: _ } => gsi,
msg => panic!("unexpected irqchip message: {msg:?}"),
}
}
#[track_caller]
fn send_ok(t: &Tube) {
t.send(&VmIrqResponse::Ok).unwrap();
}
#[test]
fn verify_ioapic_restore_cold_smoke() {
let (irqchip_tube, ioapic_irq_tube) = Tube::pair().unwrap();
let gsi_num = NUM_IOAPIC_PINS as u32;
let mut saved_ioapic = set_up_with_irq(10, TriggerMode::Level);
let snapshot = saved_ioapic.snapshot().unwrap();
let irqchip_fake = thread::spawn(move || {
assert_eq!(recv_allocate_msi(&irqchip_tube), gsi_num);
send_ok(&irqchip_tube);
let route = recv_add_msi_route(&irqchip_tube);
assert_eq!(route.gsi, gsi_num);
assert_eq!(route.msi_address, 0xa);
assert_eq!(route.msi_data, 0xd);
send_ok(&irqchip_tube);
irqchip_tube
});
let mut restored_ioapic = Ioapic::new(ioapic_irq_tube, NUM_IOAPIC_PINS).unwrap();
restored_ioapic.restore(snapshot).unwrap();
irqchip_fake.join().unwrap();
}
#[test]
fn verify_ioapic_restore_warm_smoke() {
let (irqchip_tube, ioapic_irq_tube) = Tube::pair().unwrap();
let gsi_num = NUM_IOAPIC_PINS as u32;
let mut ioapic = set_up_with_irq(10, TriggerMode::Level);
ioapic.irq_tube = ioapic_irq_tube;
let snapshot = ioapic.snapshot().unwrap();
let irqchip_fake = thread::spawn(move || {
assert_eq!(recv_release_one_irq(&irqchip_tube), gsi_num);
send_ok(&irqchip_tube);
assert_eq!(recv_allocate_msi(&irqchip_tube), gsi_num);
send_ok(&irqchip_tube);
let route = recv_add_msi_route(&irqchip_tube);
assert_eq!(route.gsi, gsi_num);
assert_eq!(route.msi_address, 0xa);
assert_eq!(route.msi_data, 0xd);
send_ok(&irqchip_tube);
irqchip_tube
});
ioapic.restore(snapshot).unwrap();
irqchip_fake.join().unwrap();
}
}