use std::sync::{
Arc,
atomic::{AtomicU32, Ordering},
};
use squib_arch::IntId;
use squib_gic::{Gic, GicError};
pub const INT_VRING: u32 = 0x01;
pub const INT_CONFIG: u32 = 0x02;
#[derive(Clone)]
pub struct IrqLine {
gic: Arc<dyn Gic + Send + Sync>,
intid: IntId,
status: Arc<AtomicU32>,
}
impl std::fmt::Debug for IrqLine {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("IrqLine")
.field("intid", &self.intid.as_raw())
.field("status", &self.status.load(Ordering::SeqCst))
.finish_non_exhaustive()
}
}
impl IrqLine {
#[must_use]
pub fn new(gic: Arc<dyn Gic + Send + Sync>, intid: IntId) -> Self {
Self {
gic,
intid,
status: Arc::new(AtomicU32::new(0)),
}
}
#[must_use]
pub fn status(&self) -> Arc<AtomicU32> {
Arc::clone(&self.status)
}
pub fn trigger_queue(&self) -> Result<(), GicError> {
self.status.fetch_or(INT_VRING, Ordering::SeqCst);
self.gic.pulse_spi(self.intid)
}
pub fn trigger_config(&self) -> Result<(), GicError> {
self.status.fetch_or(INT_CONFIG, Ordering::SeqCst);
self.gic.pulse_spi(self.intid)
}
pub fn ack(&self, mask: u32) {
self.status.fetch_and(!mask, Ordering::SeqCst);
}
}
#[cfg(test)]
mod tests {
use parking_lot::Mutex;
use super::*;
#[derive(Debug, Default)]
struct StubGic {
pulses: Mutex<Vec<u32>>,
}
impl Gic for StubGic {
fn pulse_spi(&self, intid: IntId) -> Result<(), GicError> {
self.pulses.lock().push(intid.as_raw());
Ok(())
}
fn set_spi_level(&self, _intid: IntId, _level: bool) -> Result<(), GicError> {
Ok(())
}
fn save_state(&self) -> Result<Vec<u8>, GicError> {
Ok(Vec::new())
}
fn restore_state(&self, _data: &[u8]) -> Result<(), GicError> {
Ok(())
}
}
fn line() -> (IrqLine, Arc<StubGic>) {
let gic = Arc::new(StubGic::default());
let line = IrqLine::new(gic.clone(), IntId::from_spi_cell(0).expect("intid 0"));
(line, gic)
}
#[test]
fn test_should_set_vring_bit_and_pulse_on_trigger_queue() {
let (line, gic) = line();
line.trigger_queue().unwrap();
assert_eq!(line.status().load(Ordering::SeqCst), INT_VRING);
assert_eq!(gic.pulses.lock().len(), 1);
}
#[test]
fn test_should_set_config_bit_and_pulse_on_trigger_config() {
let (line, _gic) = line();
line.trigger_config().unwrap();
assert_eq!(line.status().load(Ordering::SeqCst), INT_CONFIG);
}
#[test]
fn test_should_clear_only_acked_bits() {
let (line, _) = line();
line.trigger_queue().unwrap();
line.trigger_config().unwrap();
assert_eq!(line.status().load(Ordering::SeqCst), INT_VRING | INT_CONFIG);
line.ack(INT_VRING);
assert_eq!(line.status().load(Ordering::SeqCst), INT_CONFIG);
}
}