blob: ceca6db47e72700fc0e8687ea76292bdff37d3d6 [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! Safe interrupt handling
//!
//! Provides safe enums for decoding interrupts, suitable for use in ISR context.
use super::types::SmcController;
/// SMC interrupt types
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SmcInterrupt {
/// DMA transfer completed successfully
DmaComplete = 0,
/// DMA transfer was aborted
DmaError = 1,
/// Command was aborted
CommandAbort = 2,
/// Write protect signal active
WriteProtected = 3,
/// Unknown or no interrupt
Unknown = 255,
}
impl TryFrom<u8> for SmcInterrupt {
type Error = ();
fn try_from(value: u8) -> core::result::Result<Self, Self::Error> {
match value {
0 => Ok(SmcInterrupt::DmaComplete),
1 => Ok(SmcInterrupt::DmaError),
2 => Ok(SmcInterrupt::CommandAbort),
3 => Ok(SmcInterrupt::WriteProtected),
_ => Err(()),
}
}
}
/// Decoder for SMC interrupt status register
///
/// Safe to call from ISR context. Returns the decoded interrupt type.
pub struct SmcInterruptDecoder;
impl SmcInterruptDecoder {
/// Decode interrupt control register bits
pub fn decode(intr_ctrl: u32) -> SmcInterrupt {
Self::decode_with_context(intr_ctrl, true)
}
/// Decode interrupt bits with DMA state context.
///
/// Bit 10 indicates a command abort condition. If a DMA transfer is
/// in-flight, that abort is treated as `DmaError`; otherwise it is a
/// generic `CommandAbort`.
pub fn decode_with_context(intr_ctrl: u32, dma_in_flight: bool) -> SmcInterrupt {
// Bit fields from hardware register:
// [11] = DMA_STATUS (DMA complete)
// [10] = CMD_ABORT_STATUS (command abort / DMA abort)
// [9] = WRITE_PROTECT_STATUS
if (intr_ctrl & (1 << 11)) != 0 {
SmcInterrupt::DmaComplete
} else if (intr_ctrl & (1 << 10)) != 0 {
if dma_in_flight {
SmcInterrupt::DmaError
} else {
SmcInterrupt::CommandAbort
}
} else if (intr_ctrl & (1 << 9)) != 0 {
SmcInterrupt::WriteProtected
} else {
SmcInterrupt::Unknown
}
}
}
/// Context passed to interrupt handler
///
/// Safe to use in ISR; only allows non-blocking operations
pub struct SmcIsrContext {
pub controller_id: SmcController,
pub interrupt: SmcInterrupt,
}
#[cfg(test)]
mod tests {
use super::{SmcInterrupt, SmcInterruptDecoder};
#[test]
fn decode_emits_dma_error_on_abort_by_default() {
let intr_ctrl = 1 << 10;
assert_eq!(
SmcInterruptDecoder::decode(intr_ctrl),
SmcInterrupt::DmaError
);
}
#[test]
fn decode_with_context_can_emit_command_abort() {
let intr_ctrl = 1 << 10;
assert_eq!(
SmcInterruptDecoder::decode_with_context(intr_ctrl, false),
SmcInterrupt::CommandAbort
);
}
#[test]
fn decode_with_context_prefers_dma_complete() {
let intr_ctrl = (1 << 11) | (1 << 10);
assert_eq!(
SmcInterruptDecoder::decode_with_context(intr_ctrl, true),
SmcInterrupt::DmaComplete
);
}
}