use alloc::{string::String, sync::Arc};
use core::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use super::{
ControllerInputId, InterruptControllerId, InterruptEndpoint, InterruptSourceId,
InterruptTrigger,
};
#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
pub enum IrqError {
#[error(
"interrupt endpoint {endpoint:?} uses {actual:?} triggering, but {operation} requires \
{expected:?} triggering"
)]
InvalidTriggerMode {
endpoint: InterruptEndpoint,
operation: &'static str,
expected: InterruptTrigger,
actual: InterruptTrigger,
},
#[error("invalid interrupt operation {operation} on {endpoint:?}: {detail}")]
InvalidInput {
endpoint: InterruptEndpoint,
operation: &'static str,
detail: String,
},
#[error("unsupported interrupt operation {operation} on {endpoint:?}: {detail}")]
Unsupported {
endpoint: InterruptEndpoint,
operation: &'static str,
detail: String,
},
#[error("interrupt backend operation {operation} failed for {endpoint:?}: {detail}")]
Backend {
endpoint: InterruptEndpoint,
operation: &'static str,
detail: String,
},
}
pub type IrqResult<T = ()> = Result<T, IrqError>;
pub trait WiredIrqSink: Send + Sync {
fn set_level(&self, input: ControllerInputId, asserted: bool) -> IrqResult;
fn pulse(&self, input: ControllerInputId) -> IrqResult;
}
#[derive(Clone)]
pub struct WiredIrqInput(Arc<WiredIrqInputInner>);
struct WiredIrqInputInner {
controller: InterruptControllerId,
input: ControllerInputId,
trigger: InterruptTrigger,
next_source: AtomicU64,
asserted_sources: AtomicUsize,
level_transition: AtomicBool,
sink: Arc<dyn WiredIrqSink>,
}
impl WiredIrqInput {
pub fn new(
controller: InterruptControllerId,
input: ControllerInputId,
trigger: InterruptTrigger,
sink: Arc<dyn WiredIrqSink>,
) -> Self {
Self(Arc::new(WiredIrqInputInner {
controller,
input,
trigger,
next_source: AtomicU64::new(0),
asserted_sources: AtomicUsize::new(0),
level_transition: AtomicBool::new(false),
sink,
}))
}
pub fn connect(&self) -> IrqResult<IrqLine> {
let source = self
.0
.next_source
.try_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
current.checked_add(1)
})
.map(InterruptSourceId::new)
.map_err(|_| IrqError::InvalidInput {
endpoint: self.endpoint(),
operation: "connect interrupt source",
detail: "the input source identifier space is exhausted".into(),
})?;
Ok(IrqLine(Arc::new(IrqLineInner {
input: self.clone(),
source,
asserted: AtomicBool::new(false),
level_transition: AtomicBool::new(false),
})))
}
pub fn controller(&self) -> InterruptControllerId {
self.0.controller
}
pub fn input(&self) -> ControllerInputId {
self.0.input
}
pub fn trigger(&self) -> InterruptTrigger {
self.0.trigger
}
fn endpoint(&self) -> InterruptEndpoint {
InterruptEndpoint::Wired {
controller: self.controller(),
input: self.input(),
}
}
fn raise_source(&self) -> IrqResult {
let _transition = SerialTransition::acquire(&self.0.level_transition);
let previous = self.0.asserted_sources.load(Ordering::Relaxed);
let asserted = previous
.checked_add(1)
.ok_or_else(|| IrqError::InvalidInput {
endpoint: self.endpoint(),
operation: "raise interrupt source",
detail: "the asserted source count is exhausted".into(),
})?;
self.0.asserted_sources.store(asserted, Ordering::Relaxed);
if previous != 0 {
return Ok(());
}
if let Err(error) = self.0.sink.set_level(self.input(), true) {
self.0.asserted_sources.store(previous, Ordering::Relaxed);
return Err(error);
}
Ok(())
}
fn lower_source(&self) -> IrqResult {
let _transition = SerialTransition::acquire(&self.0.level_transition);
let previous = self.0.asserted_sources.load(Ordering::Relaxed);
if previous == 0 {
return Err(IrqError::InvalidInput {
endpoint: self.endpoint(),
operation: "lower interrupt source",
detail: "the interrupt source is not asserted".into(),
});
}
let asserted = previous - 1;
self.0.asserted_sources.store(asserted, Ordering::Relaxed);
if asserted != 0 {
return Ok(());
}
if let Err(error) = self.0.sink.set_level(self.input(), false) {
self.0.asserted_sources.store(previous, Ordering::Relaxed);
return Err(error);
}
Ok(())
}
fn disconnect_asserted_source(&self) {
let _transition = SerialTransition::acquire(&self.0.level_transition);
let previous = self.0.asserted_sources.load(Ordering::Relaxed);
if previous == 0 {
return;
}
let asserted = previous - 1;
self.0.asserted_sources.store(asserted, Ordering::Relaxed);
if asserted == 0 {
let _ = self.0.sink.set_level(self.input(), false);
}
}
}
impl core::fmt::Debug for WiredIrqInput {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("WiredIrqInput")
.field("controller", &self.controller())
.field("input", &self.input())
.field("trigger", &self.trigger())
.field(
"asserted_sources",
&self.0.asserted_sources.load(Ordering::Relaxed),
)
.finish_non_exhaustive()
}
}
#[derive(Clone)]
pub struct IrqLine(Arc<IrqLineInner>);
struct IrqLineInner {
input: WiredIrqInput,
source: InterruptSourceId,
asserted: AtomicBool,
level_transition: AtomicBool,
}
impl IrqLine {
pub fn assert(&self) -> IrqResult {
self.set_asserted("assert", true)
}
fn set_asserted(&self, operation: &'static str, asserted: bool) -> IrqResult {
self.require_trigger(operation, InterruptTrigger::LevelTriggered)?;
let _transition = SerialTransition::acquire(&self.0.level_transition);
if self.0.asserted.load(Ordering::Relaxed) == asserted {
return Ok(());
}
if asserted {
self.0.input.raise_source()?;
} else {
self.0.input.lower_source()?;
}
self.0.asserted.store(asserted, Ordering::Relaxed);
Ok(())
}
pub fn deassert(&self) -> IrqResult {
self.set_asserted("deassert", false)
}
pub fn pulse(&self) -> IrqResult {
self.require_trigger("pulse", InterruptTrigger::EdgeTriggered)?;
self.0.input.0.sink.pulse(self.input())
}
pub fn controller(&self) -> InterruptControllerId {
self.0.input.controller()
}
pub fn input(&self) -> ControllerInputId {
self.0.input.input()
}
pub fn source(&self) -> InterruptSourceId {
self.0.source
}
pub fn same_connection(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
pub fn trigger(&self) -> InterruptTrigger {
self.0.input.trigger()
}
fn require_trigger(&self, operation: &'static str, expected: InterruptTrigger) -> IrqResult {
let actual = self.trigger();
if actual != expected {
return Err(IrqError::InvalidTriggerMode {
endpoint: self.0.input.endpoint(),
operation,
expected,
actual,
});
}
Ok(())
}
}
impl core::fmt::Debug for IrqLine {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("IrqLine")
.field("controller", &self.controller())
.field("input", &self.input())
.field("source", &self.source())
.field("trigger", &self.trigger())
.field("asserted", &self.0.asserted.load(Ordering::Relaxed))
.finish()
}
}
impl Drop for IrqLineInner {
fn drop(&mut self) {
if self.asserted.load(Ordering::Relaxed) {
self.input.disconnect_asserted_source();
}
}
}
struct SerialTransition<'a> {
flag: &'a AtomicBool,
}
impl<'a> SerialTransition<'a> {
fn acquire(flag: &'a AtomicBool) -> Self {
while flag
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
while flag.load(Ordering::Acquire) {
core::hint::spin_loop();
}
}
Self { flag }
}
}
impl Drop for SerialTransition<'_> {
fn drop(&mut self) {
self.flag.store(false, Ordering::Release);
}
}