use alloc::{collections::VecDeque, vec::Vec};
use super::{
AicError, AicEvent, AicState, ControlState, IoPurpose, LinkState, MailboxState, MonotonicTime,
PendingIo, SdioRequestKind, StartupState, TxToken,
};
use crate::{
common::ChipVariant,
profile::ChipProfile,
rx::{RX_BYTE_CAPACITY, RX_CAPACITY},
tx::TxState,
};
pub(super) struct ActiveTx {
pub completion: TxCompletion,
pub wire_frame: Vec<u8>,
}
#[derive(Clone, Copy)]
pub(super) enum InternalTxKind {
M2,
M4,
}
pub(super) enum TxCompletion {
User(TxToken),
Internal(InternalTxKind),
}
pub(super) struct InternalTx {
pub kind: InternalTxKind,
pub ethernet_frame: Vec<u8>,
}
pub(super) struct LifecycleState {
pub state: AicState,
pub startup: Option<StartupState>,
pub mailbox: Option<MailboxState>,
pub control: Option<ControlState>,
pub last_time: MonotonicTime,
pub retry_at: Option<MonotonicTime>,
pub cancel_pending: bool,
}
pub(super) struct IoState {
pub pending: Option<PendingIo>,
pub next: Option<(IoPurpose, SdioRequestKind)>,
pub next_request_id: u64,
pub receive: ReceiveScan,
pub last_irq_sequence: u64,
}
pub(super) struct ReceiveScan {
pub active: bool,
pub next_path: u8,
}
pub(super) struct DataPlaneState {
pub events: VecDeque<AicEvent>,
pub event_bytes: usize,
pub tx: TxState,
pub active_tx: Option<ActiveTx>,
pub internal_tx: VecDeque<InternalTx>,
pub internal_tx_bytes: usize,
pub link: LinkState,
}
impl DataPlaneState {
pub(super) fn push_event(&mut self, event: AicEvent) -> Result<(), AicError> {
let event_bytes = match &event {
AicEvent::Receive(frame) => frame.len(),
_ => 0,
};
let is_receive = matches!(event, AicEvent::Receive(_));
while (self.events.len() >= RX_CAPACITY
|| self.event_bytes.saturating_add(event_bytes) > RX_BYTE_CAPACITY)
&& !is_receive
{
let Some(index) = self
.events
.iter()
.position(|queued| matches!(queued, AicEvent::Receive(_)))
else {
break;
};
self.remove_event(index);
}
if self.events.len() >= RX_CAPACITY
|| self.event_bytes.saturating_add(event_bytes) > RX_BYTE_CAPACITY
{
return if is_receive {
Ok(())
} else {
Err(AicError::EventQueueFull)
};
}
self.event_bytes += event_bytes;
self.events.push_back(event);
Ok(())
}
pub(super) fn pop_event(&mut self) -> Option<AicEvent> {
let event = self.events.pop_front()?;
self.event_bytes -= event_payload_bytes(&event);
Some(event)
}
pub(super) fn remove_event(&mut self, index: usize) -> Option<AicEvent> {
let event = self.events.remove(index)?;
self.event_bytes -= event_payload_bytes(&event);
Some(event)
}
pub(super) fn pop_internal_tx(&mut self) -> Option<InternalTx> {
let internal = self.internal_tx.pop_front()?;
self.internal_tx_bytes -= internal.ethernet_frame.len();
Some(internal)
}
pub(super) fn clear_internal_tx(&mut self) {
self.internal_tx.clear();
self.internal_tx_bytes = 0;
}
}
fn event_payload_bytes(event: &AicEvent) -> usize {
match event {
AicEvent::Receive(frame) => frame.len(),
_ => 0,
}
}
pub struct AicDevice {
pub(super) profile: &'static ChipProfile,
pub(super) lifecycle: LifecycleState,
pub(super) io: IoState,
pub(super) data: DataPlaneState,
}
impl AicDevice {
pub fn new(chip: ChipVariant) -> Result<Self, AicError> {
let profile = ChipProfile::for_variant(chip).ok_or(AicError::UnsupportedChip)?;
Ok(Self {
profile,
lifecycle: LifecycleState {
state: AicState::Stopped,
startup: None,
mailbox: None,
control: None,
last_time: MonotonicTime::default(),
retry_at: None,
cancel_pending: false,
},
io: IoState {
pending: None,
next: None,
next_request_id: 1,
receive: ReceiveScan {
active: false,
next_path: 0,
},
last_irq_sequence: 0,
},
data: DataPlaneState {
events: VecDeque::new(),
event_bytes: 0,
tx: TxState::new(),
active_tx: None,
internal_tx: VecDeque::new(),
internal_tx_bytes: 0,
link: LinkState::new(),
},
})
}
pub const fn state(&self) -> AicState {
self.lifecycle.state
}
pub const fn chip(&self) -> ChipVariant {
self.profile.variant()
}
pub const fn mac_address(&self) -> [u8; 6] {
match self.data.link.mac_address() {
Some(mac) => mac,
None => [0; 6],
}
}
#[cfg(any(feature = "rdif", test))]
pub(crate) fn card_irq_needed(&self) -> bool {
self.lifecycle.state == AicState::Ready || self.startup_confirmation_waiting()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dc_has_one_supported_dual_function_profile() {
let device = AicDevice::new(ChipVariant::Aic8800DC).unwrap();
assert_eq!(device.chip(), ChipVariant::Aic8800DC);
assert_eq!(device.command_function(), 2);
assert!(!device.transport_uses_header_crc());
}
#[test]
fn unsupported_variants_do_not_fall_back_to_the_dc_profile() {
for variant in [
ChipVariant::Aic8801,
ChipVariant::Aic8800DW,
ChipVariant::Aic8800D80X2,
ChipVariant::Unknown,
] {
assert!(matches!(
AicDevice::new(variant),
Err(AicError::UnsupportedChip)
));
}
}
#[test]
fn card_interrupt_is_needed_only_for_ready_or_startup_confirmation() {
let mut device = AicDevice::new(ChipVariant::Aic8800DC).unwrap();
assert!(!device.card_irq_needed());
device.lifecycle.state = AicState::Starting;
assert!(!device.card_irq_needed());
device.lifecycle.mailbox = Some(MailboxState::confirmation_for_test(
MonotonicTime::from_nanos(10),
));
assert!(device.card_irq_needed());
device.lifecycle.state = AicState::Ready;
device.lifecycle.mailbox = None;
assert!(device.card_irq_needed());
}
}