rdif-intc 0.15.0

Driver Interface of interrupt controller.
Documentation
#![no_std]

extern crate alloc;

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

pub use irq_framework::{HwIrq, IrqDomainId, IrqError, IrqId};
pub use rdif_base::{
    DriverGeneric, KError, io,
    irq::{AcpiGsiController, AcpiGsiRoute, AcpiIrqPolarity, AcpiIrqTrigger, IrqConfig, Trigger},
};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct IrqTranslation {
    pub id: IrqId,
    pub trigger: Option<Trigger>,
}

impl IrqTranslation {
    pub const fn new(id: IrqId) -> Self {
        Self { id, trigger: None }
    }

    pub const fn with_trigger(id: IrqId, trigger: Trigger) -> Self {
        Self {
            id,
            trigger: Some(trigger),
        }
    }

    pub const fn from_controller(
        domain: IrqDomainId,
        translation: ControllerIrqTranslation,
    ) -> Self {
        Self {
            id: IrqId::new(domain, translation.hwirq),
            trigger: translation.trigger,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ControllerIrqTranslation {
    pub hwirq: HwIrq,
    pub trigger: Option<Trigger>,
}

impl ControllerIrqTranslation {
    pub const fn new(hwirq: HwIrq) -> Self {
        Self {
            hwirq,
            trigger: None,
        }
    }

    pub const fn with_trigger(hwirq: HwIrq, trigger: Trigger) -> Self {
        Self {
            hwirq,
            trigger: Some(trigger),
        }
    }
}

pub trait Interface: DriverGeneric {
    fn translate_fdt(&self, _irq_prop: &[u32]) -> Result<ControllerIrqTranslation, IrqError> {
        Err(IrqError::Unsupported)
    }

    fn supports_acpi_gsi(&self, _route: &AcpiGsiRoute) -> bool {
        false
    }

    fn translate_acpi(&self, _route: &AcpiGsiRoute) -> Result<ControllerIrqTranslation, IrqError> {
        Err(IrqError::Unsupported)
    }

    fn configure(&mut self, _translation: &IrqTranslation) -> Result<(), IrqError> {
        Ok(())
    }

    fn configure_acpi(
        &mut self,
        translation: &IrqTranslation,
        _route: &AcpiGsiRoute,
    ) -> Result<(), IrqError> {
        self.configure(translation)
    }

    fn set_enabled(&mut self, _hwirq: HwIrq, _enabled: bool) -> Result<(), IrqError> {
        Err(IrqError::Unsupported)
    }
}

pub struct Intc {
    domain: IrqDomainId,
    inner: Box<dyn Interface>,
}

impl Intc {
    pub fn new<T: Interface>(domain: IrqDomainId, driver: T) -> Self {
        Self {
            domain,
            inner: Box::new(driver),
        }
    }

    pub const fn domain(&self) -> IrqDomainId {
        self.domain
    }

    pub fn translate_fdt(&self, irq_prop: &[u32]) -> Result<IrqTranslation, IrqError> {
        self.inner
            .translate_fdt(irq_prop)
            .map(|translation| IrqTranslation::from_controller(self.domain, translation))
    }

    pub fn supports_acpi_gsi(&self, route: &AcpiGsiRoute) -> bool {
        self.inner.supports_acpi_gsi(route)
    }

    pub fn translate_acpi(&self, route: &AcpiGsiRoute) -> Result<IrqTranslation, IrqError> {
        self.inner
            .translate_acpi(route)
            .map(|translation| IrqTranslation::from_controller(self.domain, translation))
    }

    pub fn configure(&mut self, translation: &IrqTranslation) -> Result<(), IrqError> {
        if translation.id.domain != self.domain {
            return Err(IrqError::InvalidIrq);
        }
        self.inner.configure(translation)
    }

    pub fn configure_acpi(
        &mut self,
        translation: &IrqTranslation,
        route: &AcpiGsiRoute,
    ) -> Result<(), IrqError> {
        if translation.id.domain != self.domain {
            return Err(IrqError::InvalidIrq);
        }
        let expected = self.translate_acpi(route)?;
        if expected.id != translation.id {
            return Err(IrqError::InvalidIrq);
        }
        self.inner.configure_acpi(translation, route)
    }

    pub fn set_enabled(&mut self, hwirq: HwIrq, enabled: bool) -> Result<(), IrqError> {
        self.inner.set_enabled(hwirq, enabled)
    }

    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 Intc {
    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 Intc {
    type Target = dyn Interface;

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

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

#[cfg(test)]
mod tests {
    extern crate std;

    use std::{
        sync::{Arc, Mutex},
        vec,
        vec::Vec,
    };

    use super::*;

    struct MockIntc {
        hwirq: HwIrq,
        enabled_calls: Arc<Mutex<Vec<(HwIrq, bool)>>>,
    }

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

    impl Interface for MockIntc {
        fn translate_fdt(&self, _irq_prop: &[u32]) -> Result<ControllerIrqTranslation, IrqError> {
            Ok(ControllerIrqTranslation::new(self.hwirq))
        }

        fn translate_acpi(
            &self,
            _route: &AcpiGsiRoute,
        ) -> Result<ControllerIrqTranslation, IrqError> {
            Ok(ControllerIrqTranslation::new(self.hwirq))
        }

        fn set_enabled(&mut self, hwirq: HwIrq, enabled: bool) -> Result<(), IrqError> {
            self.enabled_calls.lock().unwrap().push((hwirq, enabled));
            Ok(())
        }
    }

    #[test]
    fn intc_translation_uses_its_own_domain() {
        let intc_a = Intc::new(
            IrqDomainId(11),
            MockIntc {
                hwirq: HwIrq(5),
                enabled_calls: Arc::new(Mutex::new(Vec::new())),
            },
        );
        let intc_b = Intc::new(
            IrqDomainId(12),
            MockIntc {
                hwirq: HwIrq(5),
                enabled_calls: Arc::new(Mutex::new(Vec::new())),
            },
        );

        assert_eq!(
            intc_a.translate_fdt(&[5]).unwrap().id,
            IrqId::new(IrqDomainId(11), HwIrq(5))
        );
        assert_eq!(
            intc_b.translate_fdt(&[5]).unwrap().id,
            IrqId::new(IrqDomainId(12), HwIrq(5))
        );
    }

    #[test]
    fn set_enabled_passes_controller_local_hwirq() {
        let calls = Arc::new(Mutex::new(Vec::new()));
        let mut intc = Intc::new(
            IrqDomainId(11),
            MockIntc {
                hwirq: HwIrq(5),
                enabled_calls: Arc::clone(&calls),
            },
        );

        intc.set_enabled(HwIrq(5), true).unwrap();

        assert_eq!(*calls.lock().unwrap(), vec![(HwIrq(5), true)]);
    }

    #[test]
    fn configure_acpi_rejects_route_translation_mismatch() {
        let mut intc = Intc::new(
            IrqDomainId(11),
            MockIntc {
                hwirq: HwIrq(5),
                enabled_calls: Arc::new(Mutex::new(Vec::new())),
            },
        );
        let route = AcpiGsiRoute {
            gsi: 5,
            vector: 37,
            controller: AcpiGsiController::IoApic,
            controller_id: 0,
            controller_address: 0xfec0_0000,
            controller_input: 5,
            trigger: AcpiIrqTrigger::Level,
            polarity: AcpiIrqPolarity::ActiveLow,
        };
        let mismatched = IrqTranslation::new(IrqId::new(IrqDomainId(11), HwIrq(6)));

        assert_eq!(
            intc.configure_acpi(&mismatched, &route),
            Err(IrqError::InvalidIrq)
        );
    }
}