use std::io::{Error, Result};
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use dbs_device::resources::DeviceResources;
#[cfg(feature = "legacy-irq")]
use super::LegacyIrqSourceConfig;
#[cfg(feature = "msi-irq")]
use super::MsiIrqSourceConfig;
use super::{InterruptManager, InterruptSourceConfig, InterruptSourceGroup, InterruptSourceType};
pub const MSI_DEVICE_ID_SHIFT: u8 = 3;
#[cfg(feature = "legacy-irq")]
const LEGACY_CONFIGS: [InterruptSourceConfig; 1] =
[InterruptSourceConfig::LegacyIrq(LegacyIrqSourceConfig {})];
#[cfg(feature = "msi-irq")]
const MSI_INT_MASK_BIT: u8 = 0;
#[cfg(feature = "msi-irq")]
const MSI_INT_MASK: u32 = (1 << MSI_INT_MASK_BIT) as u32;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum DeviceInterruptMode {
Disabled = 0,
LegacyIrq = 1,
GenericMsiIrq = 2,
PciMsiIrq = 3,
PciMsixIrq = 4,
}
pub struct DeviceInterruptManager<T: InterruptManager> {
mode: DeviceInterruptMode,
activated: bool,
current_idx: usize,
mode2idx: [usize; 5],
intr_mgr: T,
intr_groups: Vec<Arc<Box<dyn InterruptSourceGroup>>>,
#[cfg(feature = "msi-irq")]
msi_config: Vec<InterruptSourceConfig>,
device_id: Option<u32>,
}
impl<T: InterruptManager> DeviceInterruptManager<T> {
pub fn new(intr_mgr: T, resources: &DeviceResources) -> Result<Self> {
let mut mgr = DeviceInterruptManager {
mode: DeviceInterruptMode::Disabled,
activated: false,
current_idx: usize::MAX,
mode2idx: [usize::MAX; 5],
intr_mgr,
intr_groups: Vec::new(),
#[cfg(feature = "msi-irq")]
msi_config: Vec::new(),
device_id: None,
};
#[cfg(feature = "legacy-irq")]
{
if let Some(irq) = resources.get_legacy_irq() {
let group = mgr
.intr_mgr
.create_group(InterruptSourceType::LegacyIrq, irq, 1)?;
mgr.mode2idx[DeviceInterruptMode::LegacyIrq as usize] = mgr.intr_groups.len();
mgr.intr_groups.push(group);
}
}
#[cfg(feature = "msi-irq")]
{
if let Some(msi) = resources.get_generic_msi_irqs() {
let group = mgr
.intr_mgr
.create_group(InterruptSourceType::MsiIrq, msi.0, msi.1)?;
mgr.resize_msi_config_space(group.len());
mgr.mode2idx[DeviceInterruptMode::GenericMsiIrq as usize] = mgr.intr_groups.len();
mgr.intr_groups.push(group);
}
if let Some(msi) = resources.get_pci_msi_irqs() {
let group = mgr
.intr_mgr
.create_group(InterruptSourceType::MsiIrq, msi.0, msi.1)?;
mgr.resize_msi_config_space(group.len());
mgr.mode2idx[DeviceInterruptMode::PciMsiIrq as usize] = mgr.intr_groups.len();
mgr.intr_groups.push(group);
}
if let Some(msi) = resources.get_pci_msix_irqs() {
let group = mgr
.intr_mgr
.create_group(InterruptSourceType::MsiIrq, msi.0, msi.1)?;
mgr.resize_msi_config_space(group.len());
mgr.mode2idx[DeviceInterruptMode::PciMsixIrq as usize] = mgr.intr_groups.len();
mgr.intr_groups.push(group);
}
}
Ok(mgr)
}
pub fn set_device_id(&mut self, device_id: Option<u32>) {
self.device_id = device_id;
}
pub fn is_enabled(&self) -> bool {
self.activated
}
pub fn enable(&mut self) -> Result<()> {
if self.activated {
return Ok(());
}
if self.mode == DeviceInterruptMode::Disabled
&& self.mode2idx[DeviceInterruptMode::LegacyIrq as usize] != usize::MAX
{
self.set_working_mode(DeviceInterruptMode::LegacyIrq)?;
}
if self.mode == DeviceInterruptMode::Disabled {
return Err(Error::from_raw_os_error(libc::EINVAL));
}
self.intr_groups[self.current_idx].enable(self.get_configs(self.mode))?;
self.activated = true;
Ok(())
}
pub fn reset(&mut self) -> Result<()> {
if self.activated {
self.activated = false;
self.intr_groups[self.current_idx].disable()?;
}
self.set_working_mode(DeviceInterruptMode::Disabled)?;
Ok(())
}
pub fn get_working_mode(&mut self) -> DeviceInterruptMode {
self.mode
}
pub fn set_working_mode(&mut self, mode: DeviceInterruptMode) -> Result<()> {
if self.activated {
return Err(Error::from_raw_os_error(libc::EINVAL));
}
if mode != self.mode {
if self.mode != DeviceInterruptMode::Disabled
&& self.mode != DeviceInterruptMode::LegacyIrq
&& mode != DeviceInterruptMode::LegacyIrq
&& mode != DeviceInterruptMode::Disabled
{
return Err(Error::from_raw_os_error(libc::EINVAL));
}
if mode != DeviceInterruptMode::Disabled {
self.current_idx = self.mode2idx[mode as usize];
} else {
self.reset_configs(mode);
}
self.mode = mode;
}
Ok(())
}
pub fn get_group(&self) -> Option<Arc<Box<dyn InterruptSourceGroup>>> {
if !self.activated || self.mode == DeviceInterruptMode::Disabled {
None
} else {
Some(self.intr_groups[self.current_idx].clone())
}
}
pub fn get_group_unchecked(&self) -> Arc<Box<dyn InterruptSourceGroup>> {
self.intr_groups[self.current_idx].clone()
}
#[allow(unused_variables)]
pub fn update(&mut self, index: u32) -> Result<()> {
if !self.activated {
return Err(Error::from_raw_os_error(libc::EINVAL));
}
match self.mode {
#[cfg(feature = "msi-irq")]
DeviceInterruptMode::GenericMsiIrq
| DeviceInterruptMode::PciMsiIrq
| DeviceInterruptMode::PciMsixIrq => {
let group = &self.intr_groups[self.current_idx as usize];
if index >= group.len() || index >= self.msi_config.len() as u32 {
return Err(Error::from_raw_os_error(libc::EINVAL));
}
group.update(index, &self.msi_config[index as usize])?;
Ok(())
}
_ => Err(Error::from_raw_os_error(libc::EINVAL)),
}
}
fn get_configs(&self, mode: DeviceInterruptMode) -> &[InterruptSourceConfig] {
match mode {
#[cfg(feature = "legacy-irq")]
DeviceInterruptMode::LegacyIrq => &LEGACY_CONFIGS[..],
#[cfg(feature = "msi-irq")]
DeviceInterruptMode::GenericMsiIrq
| DeviceInterruptMode::PciMsiIrq
| DeviceInterruptMode::PciMsixIrq => {
let idx = self.mode2idx[mode as usize];
let group_len = self.intr_groups[idx].len() as usize;
&self.msi_config[0..group_len]
}
_ => panic!("unhandled interrupt type in get_configs()"),
}
}
fn reset_configs(&mut self, mode: DeviceInterruptMode) {
match mode {
#[cfg(feature = "msi-irq")]
DeviceInterruptMode::GenericMsiIrq
| DeviceInterruptMode::PciMsiIrq
| DeviceInterruptMode::PciMsixIrq => {
self.msi_config = vec![
InterruptSourceConfig::MsiIrq(MsiIrqSourceConfig::default());
self.msi_config.len()
];
}
_ => {}
}
}
}
#[cfg(feature = "msi-irq")]
impl<T: InterruptManager> DeviceInterruptManager<T> {
#[allow(irrefutable_let_patterns)]
pub fn set_msi_high_address(&mut self, index: u32, data: u32) -> Result<()> {
if (index as usize) < self.msi_config.len() {
if let InterruptSourceConfig::MsiIrq(ref mut msi) = self.msi_config[index as usize] {
msi.high_addr = data;
return Ok(());
}
}
Err(Error::from_raw_os_error(libc::EINVAL))
}
#[allow(irrefutable_let_patterns)]
pub fn set_msi_low_address(&mut self, index: u32, data: u32) -> Result<()> {
if (index as usize) < self.msi_config.len() {
if let InterruptSourceConfig::MsiIrq(ref mut msi) = self.msi_config[index as usize] {
msi.low_addr = data;
return Ok(());
}
}
Err(Error::from_raw_os_error(libc::EINVAL))
}
#[allow(irrefutable_let_patterns)]
pub fn set_msi_data(&mut self, index: u32, data: u32) -> Result<()> {
if (index as usize) < self.msi_config.len() {
if let InterruptSourceConfig::MsiIrq(ref mut msi) = self.msi_config[index as usize] {
msi.data = data;
return Ok(());
}
}
Err(Error::from_raw_os_error(libc::EINVAL))
}
#[allow(irrefutable_let_patterns)]
pub fn set_msi_mask(&mut self, index: u32, mask: bool) -> Result<()> {
if (index as usize) < self.msi_config.len() {
if let InterruptSourceConfig::MsiIrq(ref mut msi) = self.msi_config[index as usize] {
let mut msg_ctl = msi.msg_ctl;
msg_ctl &= !MSI_INT_MASK;
if mask {
msg_ctl |= MSI_INT_MASK;
}
msi.msg_ctl = msg_ctl;
return Ok(());
}
}
Err(Error::from_raw_os_error(libc::EINVAL))
}
#[allow(irrefutable_let_patterns)]
pub fn get_msi_mask(&self, index: u32) -> Result<bool> {
if (index as usize) < self.msi_config.len() {
if let InterruptSourceConfig::MsiIrq(ref msi) = self.msi_config[index as usize] {
return Ok((msi.msg_ctl & MSI_INT_MASK) == MSI_INT_MASK);
}
}
Err(Error::from_raw_os_error(libc::EINVAL))
}
#[cfg(target_arch = "aarch64")]
pub fn set_msi_device_id(&mut self, index: u32) -> Result<()> {
if (index as usize) < self.msi_config.len() {
if let InterruptSourceConfig::MsiIrq(ref mut msi) = self.msi_config[index as usize] {
msi.device_id = self.device_id.map(|dev_id| {
dev_id << MSI_DEVICE_ID_SHIFT
});
return Ok(());
}
}
Err(Error::from_raw_os_error(libc::EINVAL))
}
fn resize_msi_config_space(&mut self, size: u32) {
if self.msi_config.len() < size as usize {
self.msi_config =
vec![InterruptSourceConfig::MsiIrq(MsiIrqSourceConfig::default()); size as usize];
}
}
}
#[derive(Default, Debug)]
pub struct InterruptStatusRegister32 {
status: AtomicU32,
}
impl InterruptStatusRegister32 {
pub fn new() -> Self {
InterruptStatusRegister32 {
status: AtomicU32::new(0),
}
}
pub fn read(&self) -> u32 {
self.status.load(Ordering::SeqCst)
}
pub fn write(&self, value: u32) {
self.status.store(value, Ordering::SeqCst);
}
pub fn read_and_clear(&self) -> u32 {
self.status.swap(0, Ordering::SeqCst)
}
pub fn set_bits(&self, value: u32) {
self.status.fetch_or(value, Ordering::SeqCst);
}
pub fn clear_bits(&self, value: u32) {
self.status.fetch_and(!value, Ordering::SeqCst);
}
}
#[cfg(all(test, feature = "kvm-legacy-irq", feature = "kvm-msi-irq"))]
pub(crate) mod tests {
use std::sync::Arc;
use dbs_device::resources::{DeviceResources, MsiIrqType, Resource};
use kvm_ioctls::{Kvm, VmFd};
use super::*;
use crate::KvmIrqManager;
pub(crate) fn create_vm_fd() -> VmFd {
let kvm = Kvm::new().unwrap();
kvm.create_vm().unwrap()
}
fn create_init_resources() -> DeviceResources {
let mut resources = DeviceResources::new();
resources.append(Resource::MmioAddressRange {
base: 0xd000_0000,
size: 0x10_0000,
});
resources.append(Resource::LegacyIrq(0));
resources.append(Resource::MsiIrq {
ty: MsiIrqType::GenericMsi,
base: 0x200,
size: 0x10,
});
resources.append(Resource::MsiIrq {
ty: MsiIrqType::PciMsi,
base: 0x100,
size: 0x20,
});
resources.append(Resource::MsiIrq {
ty: MsiIrqType::PciMsix,
base: 0x300,
size: 0x30,
});
resources
}
fn create_interrupt_manager() -> DeviceInterruptManager<Arc<KvmIrqManager>> {
let vmfd = Arc::new(create_vm_fd());
vmfd.create_irq_chip().unwrap();
let intr_mgr = Arc::new(KvmIrqManager::new(vmfd));
let resource = create_init_resources();
intr_mgr.initialize().unwrap();
DeviceInterruptManager::new(intr_mgr, &resource).unwrap()
}
#[test]
fn test_create_device_interrupt_manager() {
let mut mgr = create_interrupt_manager();
assert_eq!(mgr.mode, DeviceInterruptMode::Disabled);
assert!(!mgr.activated);
assert_eq!(mgr.current_idx, usize::MAX);
assert_eq!(mgr.intr_groups.len(), 4);
assert!(!mgr.is_enabled());
assert!(mgr.get_group().is_none());
mgr.enable().unwrap();
assert!(mgr.is_enabled());
assert_eq!(
mgr.mode2idx[DeviceInterruptMode::LegacyIrq as usize],
mgr.current_idx
);
assert!(mgr.get_group().is_some());
assert_eq!(
mgr.get_group_unchecked().interrupt_type(),
InterruptSourceType::LegacyIrq
);
mgr.reset().unwrap();
assert!(!mgr.is_enabled());
assert_eq!(
mgr.mode2idx[DeviceInterruptMode::LegacyIrq as usize],
mgr.current_idx
);
assert_eq!(mgr.get_working_mode(), DeviceInterruptMode::Disabled);
assert!(mgr.get_group().is_none());
}
#[test]
fn test_device_interrupt_manager_switch_mode() {
let mut mgr = create_interrupt_manager();
mgr.enable().unwrap();
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
.unwrap_err();
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
.unwrap_err();
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
.unwrap_err();
mgr.reset().unwrap();
mgr.set_working_mode(DeviceInterruptMode::LegacyIrq)
.unwrap();
mgr.set_working_mode(DeviceInterruptMode::LegacyIrq)
.unwrap();
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
.unwrap();
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
.unwrap();
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
.unwrap_err();
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
.unwrap_err();
mgr.set_working_mode(DeviceInterruptMode::LegacyIrq)
.unwrap();
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
.unwrap();
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
.unwrap();
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
.unwrap_err();
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
.unwrap_err();
mgr.set_working_mode(DeviceInterruptMode::LegacyIrq)
.unwrap();
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
.unwrap();
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
.unwrap();
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
.unwrap_err();
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
.unwrap_err();
mgr.set_working_mode(DeviceInterruptMode::Disabled).unwrap();
mgr.set_working_mode(DeviceInterruptMode::Disabled).unwrap();
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
.unwrap();
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
.unwrap_err();
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
.unwrap_err();
mgr.set_working_mode(DeviceInterruptMode::Disabled).unwrap();
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
.unwrap();
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
.unwrap_err();
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
.unwrap_err();
mgr.set_working_mode(DeviceInterruptMode::Disabled).unwrap();
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
.unwrap();
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
.unwrap_err();
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
.unwrap_err();
mgr.set_working_mode(DeviceInterruptMode::Disabled).unwrap();
mgr.set_working_mode(DeviceInterruptMode::Disabled).unwrap();
}
#[test]
fn test_msi_config() {
let mut interrupt_manager = create_interrupt_manager();
assert!(interrupt_manager.set_msi_data(512, 0).is_err());
interrupt_manager.set_msi_data(0, 0).unwrap();
assert!(interrupt_manager.set_msi_high_address(512, 0).is_err());
interrupt_manager.set_msi_high_address(0, 0).unwrap();
assert!(interrupt_manager.set_msi_low_address(512, 0).is_err());
interrupt_manager.set_msi_low_address(0, 0).unwrap();
assert!(interrupt_manager.get_msi_mask(512).is_err());
assert!(!interrupt_manager.get_msi_mask(0).unwrap());
assert!(interrupt_manager.set_msi_mask(512, true).is_err());
interrupt_manager.set_msi_mask(0, true).unwrap();
assert!(interrupt_manager.get_msi_mask(0).unwrap());
}
#[test]
fn test_set_working_mode_after_activated() {
let mut interrupt_manager = create_interrupt_manager();
interrupt_manager.activated = true;
assert!(interrupt_manager
.set_working_mode(DeviceInterruptMode::Disabled)
.is_err());
assert!(interrupt_manager
.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
.is_err());
assert!(interrupt_manager
.set_working_mode(DeviceInterruptMode::LegacyIrq)
.is_err());
assert!(interrupt_manager
.set_working_mode(DeviceInterruptMode::PciMsiIrq)
.is_err());
assert!(interrupt_manager
.set_working_mode(DeviceInterruptMode::PciMsixIrq)
.is_err());
}
#[test]
fn test_disable2legacy() {
let mut interrupt_manager = create_interrupt_manager();
interrupt_manager.activated = false;
interrupt_manager.mode = DeviceInterruptMode::Disabled;
interrupt_manager
.set_working_mode(DeviceInterruptMode::LegacyIrq)
.unwrap();
}
#[test]
fn test_disable2nonlegacy() {
let mut interrupt_manager = create_interrupt_manager();
interrupt_manager.activated = false;
interrupt_manager.mode = DeviceInterruptMode::Disabled;
interrupt_manager
.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
.unwrap();
}
#[test]
fn test_legacy2nonlegacy() {
let mut interrupt_manager = create_interrupt_manager();
interrupt_manager.activated = false;
interrupt_manager.mode = DeviceInterruptMode::Disabled;
interrupt_manager
.set_working_mode(DeviceInterruptMode::LegacyIrq)
.unwrap();
interrupt_manager
.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
.unwrap();
}
#[test]
fn test_nonlegacy2legacy() {
let mut interrupt_manager = create_interrupt_manager();
interrupt_manager.activated = false;
interrupt_manager.mode = DeviceInterruptMode::Disabled;
interrupt_manager
.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
.unwrap();
interrupt_manager
.set_working_mode(DeviceInterruptMode::LegacyIrq)
.unwrap();
}
#[test]
fn test_update() {
let mut interrupt_manager = create_interrupt_manager();
interrupt_manager
.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
.unwrap();
interrupt_manager.enable().unwrap();
assert!(interrupt_manager.update(0x10).is_err());
interrupt_manager.update(0x01).unwrap();
interrupt_manager.reset().unwrap();
interrupt_manager
.set_working_mode(DeviceInterruptMode::LegacyIrq)
.unwrap();
assert!(interrupt_manager.update(0x10).is_err());
}
#[test]
fn test_get_configs() {
{
let interrupt_manager = create_interrupt_manager();
let legacy_config = interrupt_manager.get_configs(DeviceInterruptMode::LegacyIrq);
assert_eq!(legacy_config, LEGACY_CONFIGS);
}
{
let mut interrupt_manager = create_interrupt_manager();
interrupt_manager
.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
.unwrap();
let msi_config = interrupt_manager.get_configs(DeviceInterruptMode::GenericMsiIrq);
assert_eq!(msi_config.len(), 0x10);
}
{
let mut interrupt_manager = create_interrupt_manager();
interrupt_manager
.set_working_mode(DeviceInterruptMode::PciMsiIrq)
.unwrap();
let msi_config = interrupt_manager.get_configs(DeviceInterruptMode::PciMsiIrq);
assert_eq!(msi_config.len(), 0x20);
}
{
let mut interrupt_manager = create_interrupt_manager();
interrupt_manager
.set_working_mode(DeviceInterruptMode::PciMsixIrq)
.unwrap();
let msi_config = interrupt_manager.get_configs(DeviceInterruptMode::PciMsixIrq);
assert_eq!(msi_config.len(), 0x30);
}
}
#[test]
fn test_reset_configs() {
let mut interrupt_manager = create_interrupt_manager();
interrupt_manager.reset_configs(DeviceInterruptMode::LegacyIrq);
interrupt_manager.reset_configs(DeviceInterruptMode::LegacyIrq);
interrupt_manager.set_msi_data(0, 100).unwrap();
interrupt_manager.set_msi_high_address(0, 200).unwrap();
interrupt_manager.set_msi_low_address(0, 300).unwrap();
interrupt_manager.reset_configs(DeviceInterruptMode::GenericMsiIrq);
assert_eq!(
interrupt_manager.msi_config[0],
InterruptSourceConfig::MsiIrq(MsiIrqSourceConfig::default())
);
}
#[test]
fn test_interrupt_status_register() {
let status = InterruptStatusRegister32::new();
assert_eq!(status.read(), 0);
status.write(0x13);
assert_eq!(status.read(), 0x13);
status.clear_bits(0x11);
assert_eq!(status.read(), 0x2);
status.set_bits(0x100);
assert_eq!(status.read_and_clear(), 0x102);
assert_eq!(status.read(), 0);
}
}