rdif-msi 0.1.0

Driver Interface for message-signaled interrupt providers.
Documentation
#![no_std]

extern crate alloc;

use alloc::{boxed::Box, vec::Vec};
use core::ops::{Deref, DerefMut};

pub use irq_framework::{IrqAffinity, IrqError, IrqId};
pub use rdif_base::DriverGeneric;

#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MsiProviderId(pub u64);

#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MsiDeviceId(pub u32);

#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct MsiEventId(pub u32);

#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct MsiVectorIndex(pub u16);

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MsiMessage {
    pub address: u64,
    pub data: u32,
}

impl MsiMessage {
    pub const fn new(address: u64, data: u32) -> Self {
        Self { address, data }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MsiVector {
    pub index: MsiVectorIndex,
    pub event: MsiEventId,
    pub irq: IrqId,
}

impl MsiVector {
    pub const fn new(index: MsiVectorIndex, event: MsiEventId, irq: IrqId) -> Self {
        Self { index, event, irq }
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MsiAllocation {
    provider: MsiProviderId,
    device: MsiDeviceId,
    vectors: Box<[MsiVector]>,
}

impl MsiAllocation {
    pub fn new(provider: MsiProviderId, device: MsiDeviceId, vectors: Box<[MsiVector]>) -> Self {
        Self {
            provider,
            device,
            vectors,
        }
    }

    pub const fn provider(&self) -> MsiProviderId {
        self.provider
    }

    pub const fn device(&self) -> MsiDeviceId {
        self.device
    }

    pub fn vectors(&self) -> &[MsiVector] {
        &self.vectors
    }

    pub fn into_vectors(self) -> Box<[MsiVector]> {
        self.vectors
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MsiRequest {
    pub device: MsiDeviceId,
    pub vector_count: u16,
    pub affinity: IrqAffinity,
}

impl MsiRequest {
    pub const fn new(device: MsiDeviceId, vector_count: u16) -> Self {
        Self {
            device,
            vector_count,
            affinity: IrqAffinity::Any,
        }
    }

    pub const fn affinity(mut self, affinity: IrqAffinity) -> Self {
        self.affinity = affinity;
        self
    }
}

pub trait Interface: DriverGeneric {
    fn allocate_vectors(&mut self, request: &MsiRequest) -> Result<Vec<MsiVector>, IrqError>;

    fn compose_message(&self, vector: &MsiVector) -> Result<MsiMessage, IrqError>;

    fn set_vector_enabled(&mut self, _vector: &MsiVector, _enabled: bool) -> Result<(), IrqError> {
        Err(IrqError::Unsupported)
    }

    fn set_vector_affinity(
        &mut self,
        _vector: &MsiVector,
        _affinity: IrqAffinity,
    ) -> Result<(), IrqError> {
        Err(IrqError::Unsupported)
    }

    fn free_vectors(&mut self, allocation: MsiAllocation) -> Result<(), IrqError>;
}

pub struct Msi {
    provider: MsiProviderId,
    inner: Box<dyn Interface>,
}

impl Msi {
    pub fn new<T: Interface>(provider: MsiProviderId, driver: T) -> Self {
        Self {
            provider,
            inner: Box::new(driver),
        }
    }

    pub const fn provider(&self) -> MsiProviderId {
        self.provider
    }

    pub fn allocate(&mut self, request: MsiRequest) -> Result<MsiAllocation, IrqError> {
        if request.vector_count == 0 {
            return Err(IrqError::InvalidIrq);
        }
        let vectors = self.inner.allocate_vectors(&request)?;
        if vectors.len() != usize::from(request.vector_count) {
            return Err(IrqError::InvalidIrq);
        }
        Ok(MsiAllocation::new(
            self.provider,
            request.device,
            vectors.into_boxed_slice(),
        ))
    }

    pub fn compose_message(&self, vector: &MsiVector) -> Result<MsiMessage, IrqError> {
        self.inner.compose_message(vector)
    }

    pub fn set_vector_enabled(
        &mut self,
        vector: &MsiVector,
        enabled: bool,
    ) -> Result<(), IrqError> {
        self.inner.set_vector_enabled(vector, enabled)
    }

    pub fn set_vector_affinity(
        &mut self,
        vector: &MsiVector,
        affinity: IrqAffinity,
    ) -> Result<(), IrqError> {
        self.inner.set_vector_affinity(vector, affinity)
    }

    pub fn free(&mut self, allocation: MsiAllocation) -> Result<(), IrqError> {
        if allocation.provider != self.provider {
            return Err(IrqError::InvalidIrq);
        }
        self.inner.free_vectors(allocation)
    }

    pub fn typed_ref<T: Interface>(&self) -> Option<&T> {
        self.raw_any()?.downcast_ref()
    }

    pub fn typed_mut<T: Interface>(&mut self) -> Option<&mut T> {
        self.raw_any_mut()?.downcast_mut()
    }
}

impl DriverGeneric for Msi {
    fn name(&self) -> &str {
        self.inner.name()
    }

    fn raw_any(&self) -> Option<&dyn core::any::Any> {
        Some(self.inner.as_ref() as &dyn core::any::Any)
    }

    fn raw_any_mut(&mut self) -> Option<&mut dyn core::any::Any> {
        Some(self.inner.as_mut() as &mut dyn core::any::Any)
    }
}

impl Deref for Msi {
    type Target = dyn Interface;

    fn deref(&self) -> &Self::Target {
        self.inner.as_ref()
    }
}

impl DerefMut for Msi {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.inner.as_mut()
    }
}

#[cfg(test)]
mod tests {
    use alloc::{boxed::Box, vec::Vec};
    use core::cell::RefCell;

    extern crate alloc;

    use irq_framework::{HwIrq, IrqDomainId, IrqError, IrqId};
    use rdif_base::DriverGeneric;

    use crate::{
        Interface, Msi, MsiAllocation, MsiDeviceId, MsiEventId, MsiMessage, MsiProviderId,
        MsiRequest, MsiVector, MsiVectorIndex,
    };

    struct MockProvider {
        freed: RefCell<Vec<MsiAllocation>>,
    }

    impl DriverGeneric for MockProvider {
        fn name(&self) -> &str {
            "mock-msi"
        }
    }

    impl Interface for MockProvider {
        fn allocate_vectors(&mut self, request: &MsiRequest) -> Result<Vec<MsiVector>, IrqError> {
            Ok((0..request.vector_count)
                .map(|index| {
                    MsiVector::new(
                        MsiVectorIndex(index),
                        MsiEventId(32 + u32::from(index)),
                        IrqId::new(IrqDomainId(7), HwIrq(8192 + u32::from(index))),
                    )
                })
                .collect())
        }

        fn compose_message(&self, vector: &MsiVector) -> Result<MsiMessage, IrqError> {
            Ok(MsiMessage::new(0x0808_0000, vector.event.0))
        }

        fn free_vectors(&mut self, allocation: MsiAllocation) -> Result<(), IrqError> {
            self.freed.borrow_mut().push(allocation);
            Ok(())
        }
    }

    #[test]
    fn allocation_records_provider_device_and_vectors() {
        let provider = MsiProviderId(3);
        let mut msi = Msi::new(
            provider,
            MockProvider {
                freed: RefCell::new(Vec::new()),
            },
        );

        let allocation = msi
            .allocate(MsiRequest::new(MsiDeviceId(0x1234), 2))
            .unwrap();

        assert_eq!(allocation.provider(), provider);
        assert_eq!(allocation.device(), MsiDeviceId(0x1234));
        assert_eq!(allocation.vectors().len(), 2);
        assert_eq!(allocation.vectors()[1].index, MsiVectorIndex(1));
        assert_eq!(
            msi.compose_message(&allocation.vectors()[1]).unwrap(),
            MsiMessage::new(0x0808_0000, 33)
        );
    }

    #[test]
    fn freeing_allocation_rejects_wrong_provider() {
        let mut msi = Msi::new(
            MsiProviderId(7),
            MockProvider {
                freed: RefCell::new(Vec::new()),
            },
        );
        let wrong = MsiAllocation::new(
            MsiProviderId(8),
            MsiDeviceId(1),
            Box::new([MsiVector::new(
                MsiVectorIndex(0),
                MsiEventId(4),
                IrqId::new(IrqDomainId(7), HwIrq(8192)),
            )]),
        );

        assert_eq!(msi.free(wrong), Err(IrqError::InvalidIrq));
    }
}