// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The secure digital card interface of RM0090 rev 22 chapter 31, written against the `sdio` layout.
//!
//! Section 31.3.1 gives the adapter five subunits and this model is those five: the register block,
//! the control unit, the command path, the data path, and the data FIFO. What's on the far end of
//! the bus isn't one of them. A card is [`crate::chip::sd::Card`], which this calls and never looks
//! inside. The manual splits the two the same way: 31.3 is the adapter and 31.4 is the card, and
//! keeping them apart is what makes each half ordinary.
//!
//! The command path settles inside the access that starts it and the data path runs on the clock.
//! That's the one asymmetry worth knowing before reading further, and the second table says why.
//!
//! | Register or section | Behavior | Source | Test |
//! |---|---|---|---|
//! | `POWER` | `PWRCTRL` off, and the interface answers no command until it's on. The two reserved encodings are the off state, which is what the page leaves them as | 31.9.1 | `the_interface_sends_nothing_until_it_is_powered` |
//! | `CLKCR` `CLKDIV`, `BYPASS` | `SDIO_CK` is `SDIOCLK / [CLKDIV + 2]`, or `SDIOCLK` itself under `BYPASS`, which is what every transfer is timed against | 31.9.2 | `a_block_takes_the_time_the_divider_gives_it`, `the_bypass_runs_the_bus_at_the_adapter_clock` |
//! | `SDIOCLK` | The 48 MHz domain as the part's tree feeds it, `PLL48CLK` of section 6.2's figure 16, followed as it changes: the next transfer is timed at the new rate | 31.3, 6.2 | `a_change_of_clock_applies_to_the_next_transfer` |
//! | `CLKCR` `CLKEN`, `WIDBUS` | The clock enabled, and one line or four. A wider bus moves a block in proportionately less time | 31.9.2 | `a_wide_bus_moves_a_block_in_a_quarter_of_the_time` |
//! | `ARG`, `CMD` `CMDINDEX`, `CPSMEN` | A write with the state machine enabled sends that index carrying that argument | 31.9.3, 31.9.4 | `a_command_reaches_the_card_with_its_argument` |
//! | `CMD` `WAITRESP` | `00` and `10` expect no response and raise `CMDSENT`. `01` takes a short one into `RESP1` and `11` a long one into `RESP1` to `RESP4`, each raising `CMDREND` | 31.9.4 | `a_short_response_lands_in_the_first_register`, `a_long_response_fills_all_four`, `a_command_that_wants_no_response_is_sent_and_no_more` |
//! | `RESPCMD` | The index the card answered, which a host checks against the one it sent | 31.9.5 | `a_short_response_lands_in_the_first_register` |
//! | `STA` `CTIMEOUT` | A card that does not answer, and a bus with no card at all | 31.9.11 | `a_command_no_card_answers_times_out` |
//! | `STA` `CCRCFAIL` | A response whose check does not hold | 31.9.11 | `a_response_that_does_not_check_is_flagged` |
//! | `DCTRL` `DTEN`, `DTDIR`, `DBLOCKSIZE`, `DLEN` | A transfer of `DLEN` bytes in blocks of `1 << DBLOCKSIZE`, in the direction `DTDIR` gives | 31.9.8, 31.9.9 | `a_block_read_reaches_the_fifo_word_by_word`, `a_block_written_reaches_the_card` |
//! | `DCOUNT` | What is left of `DLEN`, counting down as the bus moves it | 31.9.10 | `the_counter_counts_the_bytes_that_are_left` |
//! | `STA` `RXACT`, `TXACT`, `DBCKEND`, `DATAEND` | `RXACT` and `TXACT` in progress, `DBCKEND` a block done, `DATAEND` the count reached zero | 31.9.11 | `a_block_read_reaches_the_fifo_word_by_word`, `a_transfer_that_runs_out_ends_and_says_so` |
//! | `STA` `DCRCFAIL` | A block the card gives whose check does not hold | 31.9.11 | `a_block_that_does_not_check_is_flagged` |
//! | `STA` `RXOVERR`, `TXUNDERR` | A receive FIFO nothing drains, and a transmit FIFO nothing fills | 31.9.11 | `a_receive_nobody_drains_overruns`, `a_transmit_nobody_fills_underruns` |
//! | `STA` `DTIMEOUT` | A card with no block to give stalls the data path rather than faulting it, and `DTIMER` card clock periods later the timer ends it. That is the only thing that does, which is why a firmware that leaves `DTIMER` at its reset value waits forever | 31.9.7, 31.9.11 | `a_transfer_whose_card_never_answers_times_out` |
//! | `FIFO` and the eight FIFO flags | Thirty-two words, with empty, full, half and available for each direction | 31.9.11, 31.9.15 | `the_fifo_flags_follow_what_is_in_it` |
//! | `FIFOCNT` | "The remaining number of words to be written to or read from the FIFO", loaded from `SDIO_DLEN` when the transfer is enabled, with "the remaining 1 to 3 bytes regarded as a word". It's what is left of the transfer, not what the FIFO holds | 31.9.14 | `the_fifo_flags_follow_what_is_in_it`, `a_block_takes_the_time_the_divider_gives_it` |
//! | `ICR` | Each flag cleared by its own bit and by no other | 31.9.12 | `every_flag_is_cleared_by_its_own_bit_and_no_other` |
//! | `MASK` | One line, the level of any flag standing with its own enable set | 31.9.13 | `every_flag_raises_the_line_only_once_its_own_enable_is_set` |
//! | `DCTRL` `DMAEN` | The request the controller drives for a stream to follow: the receive FIFO half full, or the transmit half empty. And once the count is out, whatever the receive FIFO still holds, section 10.3.15's last single requests | 31.9.9, 10.3.2, 10.3.15 | `a_stream_moves_a_block_without_the_core_touching_a_word` |
//! | The last-data signal | The second line the controller drives, high while `DATAEND` stands: "the peripheral indicates by hardware to the DMA controller when the last data are being transferred", which a stream with the peripheral as flow controller ends on | 10.3.15, 31.3.2 step 4d | `a_stream_the_peripheral_controls_ends_when_it_signals_the_last_data`, `a_write_the_peripheral_controls_ends_the_same_way` |
//! | ES0206 rev 24, 2.4.2 | The check is calculated for a response that carries none, so `CCRCFAIL` stands after one. Table 175 gives `R3` all ones where a check would be, `ACMD41` answers `R3`, and a firmware that does not ignore the flag there fails on the part | modeled | `a_response_with_no_check_still_flags_the_erratum` |
//! | A silent card | A card the board has silenced, a board condition, is an empty slot to the interface: a command times out with `CTIMEOUT`, and a block it would have given or taken never moves, so the data timer ends the transfer with `DTIMEOUT`, between two blocks of a transfer as at its start. It answers again as the card it was | not applicable | `a_silent_card_is_an_empty_slot_and_comes_back_as_it_was`, `a_card_silenced_mid_read_ends_it_with_a_data_timeout`, `a_block_written_to_a_silent_card_never_lands` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behavior | Source | Reason |
//! |---|---|---|
//! | A transfer under way when `SDIOCLK` changes, and a transfer begun with `SDIOCLK` at nothing | 31.3 | The first finishes at the rate it began at, where the part would clock its remaining words at the new one. A tree isn't retuned with a block in flight. The second is timed at one hertz rather than never: the domain is nothing until the PLL locks, and no firmware starts a transfer before it does |
//! | The command's own time on the wire: forty-eight bits out and forty-eight back at `SDIO_CK` | 31.3.1 | The command path settles inside the access that starts it, so a host polling `CMDREND` leaves its loop on the first read. The page gives the widths but a host has no way to observe the gap except by polling, and erring early is the direction that cannot hang a firmware. The data path is timed, because that is what a transfer's length is asserted against and what `RXOVERR` needs to be reachable |
//! | CE-ATA: `ATACMD`, `ENCMDcompl`, `nIEN`, `CEATAEND`, and 31.7 entire | 31.7 | A different device class over the same electrical interface, and nothing on this board is one. ES0206 rev 24 2.4.4 is its erratum, unreachable here for the same reason, though Table 3 marks it on every revision |
//! | SD I/O: `SDIOEN`, `SDIOSuspend`, `SDIOIT`, the read wait of `RWSTART`, `RWSTOP` and `RWMOD`, and 31.6 entire | 31.6, 31.4.13 | A different card class. Section 31.1 says several memory commands "are not supported by either SD I/O-only cards or the I/O portion of combo cards". The bits are stored and nothing acts on them |
//! | Stream mode, `DTMODE` set | 31.4.7 | The section's own title says MultiMediaCard only. The bit is stored and a transfer runs in blocks whatever it says |
//! | The eight-wide bus, `WIDBUS` at `10` | 31.1, 31.9.2 | The encoding is there and is stored and read back. Eight lines are a MultiMediaCard's. Section 31.1 gives an SD I/O card two modes and a memory card no more, so no card this board takes answers on eight |
//! | `STBITERR` | 31.9.11 | The start bit is a thing on the data lines and this bus moves blocks, not bits. A card that fails to start is a card that gives no block, which is `DTIMEOUT` |
//! | `PWRSAV` | 31.9.2 | It stops `SDIO_CK` between transfers, which saves power the model does not account and changes nothing a firmware can observe |
//! | ES0206 rev 24, 2.4.1, wrong data under hardware flow control | 31.8 | "Glitches **may** occur", which a deterministic run cannot reproduce and a distribution would invent. `HWFC_EN` is stored, and the errata sheet's own advice is not to use it |
//! | ES0206 rev 24, 2.4.3, corruption under `NEGEDGE` | 31.9.2 | "May lead to invalid data", for the same reason. `NEGEDGE` is stored and the advice is to avoid it |
//! | ES0206 rev 24, 2.4.5, an underrun going undetected and a wrong word sent, under one relation of the clocks | 31.9.2, 31.9.11 | Table 3 marks it on every revision. `TXUNDERR` rises whenever the transmit FIFO runs dry, whatever the clocks |
//! | The three `SDIOCLK` periods a second write to `SDIO_POWER` must wait | 31.9.1 | A delay between two writes the host makes, which the page tells the software to take and names no flag for. Nothing here can observe it |
use std::collections::VecDeque;
use crate::chip::sd::{Answer, Card, Outcome};
use crate::chip::{Place, Placed, Timeline};
use crate::device::{Device, DeviceContext, Level, RegisterFile};
use crate::snapshot::store;
use crate::soc::stm32f427::generated::sdio::{Register, clkcr, cmd, dctrl, icr, power, sta};
use crate::time::{EventId, VirtualNanos};
const NANOS_PER_SECOND: u128 = 1_000_000_000;
/// Words in the data FIFO, 31.9.15.
const FIFO_WORDS: usize = 32;
/// What "half" means for `RXFIFOHF` and `TXFIFOHE`, 31.9.11: "at least 8 words".
const HALF: usize = 8;
const BYTES_PER_WORD: u64 = 4;
/// The interrupt this instance lists first, its global interrupt, RM0090 rev 22 table 63.
const GLOBAL: usize = 0;
/// The request a stream follows, 31.9.9 and section 10.3.2.
///
/// One line for both directions, because one transfer runs at a time and `DTDIR` says which way it goes.
pub const TRANSFER_REQUEST: usize = 0;
/// The last-data signal, this instance's request output one: the level a stream whose flow controller is
/// this peripheral ends on, RM0090 rev 22 section 10.3.15 and 31.3.2 step 4d. High while `DATAEND` stands.
pub const LAST_DATA_REQUEST: usize = 1;
/// `PWRCTRL` of 31.9.1: `11` is the interface on and every other encoding is off.
const POWER_ON: u64 = 0b11;
/// `WAITRESP` of 31.9.4.
const WAIT_NO_RESPONSE: u64 = 0b00;
const WAIT_SHORT: u64 = 0b01;
const WAIT_NO_RESPONSE_TOO: u64 = 0b10;
const WAIT_LONG: u64 = 0b11;
/// `WIDBUS` of 31.9.2.
const FOUR_LINES: u64 = 0b01;
/// Which way a transfer runs, `DTDIR` of 31.9.9.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Direction {
/// `0`: from the controller to the card.
ToCard,
/// `1`: from the card to the controller.
FromCard,
}
/// A transfer in flight, 31.9.8 to 31.9.10.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct Transfer {
direction: Direction,
/// Bytes in a block, `1 << DBLOCKSIZE`.
block: u64,
/// The instant the transfer was enabled, which every word's instant is measured from.
started: VirtualNanos,
/// Nanoseconds one word of the bus takes, from `SDIO_CK` and the bus width.
per_word: u128,
/// Words the bus has moved, which with `per_word` gives where it has got to.
moved: u64,
/// Bytes still to move, which is `DCOUNT`.
remaining: u64,
/// The block being taken from the card or given to it, filled a word at a time.
buffer: Vec<u8>,
/// How much of `buffer` a read has already put on the bus.
taken: usize,
/// The card's own timeout in nanoseconds, from `DTIMER` in card clock periods.
timeout: u128,
/// The instant the timeout is measured from: the transfer's start, then each block that crosses.
since: VirtualNanos,
/// The card has no block to give or will take no more, so nothing moves until the timer ends it.
stalled: bool,
}
/// The secure digital card interface.
#[derive(Debug)]
pub struct Sdio {
clock_hz: u64,
card: Option<Place<dyn Card>>,
fifo: VecDeque<u32>,
transfer: Option<Transfer>,
request: Level,
last_data: Level,
/// Whether the transfer that last ended was a receive, whose tail the request still asks for.
received_last: bool,
interrupt: Level,
wakeup: Option<(VirtualNanos, EventId)>,
}
store!(Sdio {
values: [
clock_hz,
fifo,
transfer,
request,
last_data,
received_last,
interrupt,
wakeup,
],
parts: [card],
built: [],
});
impl Sdio {
/// An interface whose `SDIOCLK` runs at `clock_hz`, which section 31.3 gives as up to 50 MHz, 48 when
/// the part also runs USB, until the part's tree drives it otherwise.
#[must_use]
pub fn with_clock(clock_hz: u64) -> Self {
Self {
clock_hz,
card: None,
fifo: VecDeque::with_capacity(FIFO_WORDS),
transfer: None,
request: Level::Low,
last_data: Level::Low,
received_last: false,
interrupt: Level::Low,
wakeup: None,
}
}
/// Puts a card in the slot.
pub fn attach(&mut self, card: impl Card + 'static) {
let card: Box<dyn Card> = Box::new(card);
self.card = Some(Place::new(card));
}
/// The card in the slot, for a test that wants the far end's own record.
#[must_use]
pub fn card(&self) -> Option<&dyn Card> {
self.card.as_ref().map(Place::chip)
}
/// The card in the slot as the type it was put there as, to drive or read what it holds. `None`
/// with the slot empty or another card in it.
pub fn card_mut<C: Card>(&mut self) -> Option<&mut C> {
self.card.as_mut()?.chip_as()
}
/// Tells the card in the slot that the part restarted, at the instant `context` gives.
pub(crate) fn chips_restarted(&mut self, context: &mut DeviceContext<'_>) {
if let Some(place) = &mut self.card {
place.chip_mut().restarted(&mut Timeline::new(context));
}
}
/// Takes the card out of `from`'s slot and puts it in this one, silent if it was.
pub fn inherit(&mut self, from: &mut Self) {
self.card = from.card.take();
}
/// Stops the card answering, or lets it answer again: a board condition, [`Place::silence`]. The
/// interface then takes the path it takes with the slot empty. Answers whether a card is in the
/// slot.
#[must_use]
pub fn silence(&mut self, silent: bool) -> bool {
self.card
.as_mut()
.map(|place| place.silence(silent))
.is_some()
}
/// The card's place in the slot, for a trigger to count and a one-shot fault to reach. Each
/// command is a transaction.
pub fn place_mut(&mut self) -> Option<&mut dyn Placed> {
self.card.as_mut().map(|place| -> &mut dyn Placed { place })
}
/// Whether a card is in the slot at all, which isn't the same as one that answers nothing.
#[must_use]
pub fn attached(&self) -> bool {
self.card.is_some()
}
/// `SDIO_CK`, 31.9.2: `SDIOCLK / [CLKDIV + 2]`, or `SDIOCLK` itself under `BYPASS`.
fn card_clock(&self, registers: &RegisterFile) -> u64 {
let control = registers.get(Register::Clkcr);
if clkcr::BYPASS.is_set(control) {
return self.clock_hz.max(1);
}
let divider = clkcr::CLKDIV.get(control) + 2;
(self.clock_hz / divider).max(1)
}
/// How many lines carry the data, 31.9.2.
///
/// Eight is a `MultiMediaCard`'s and no card this board takes answers on it, so `10` reads back as
/// written and moves data on one line like the default the page gives it.
fn lines(registers: &RegisterFile) -> u64 {
if clkcr::WIDBUS.get(registers.get(Register::Clkcr)) == FOUR_LINES {
4
} else {
1
}
}
/// Whether the interface is powered, 31.9.1.
fn powered(registers: &RegisterFile) -> bool {
power::PWRCTRL.get(registers.get(Register::Power)) == POWER_ON
}
fn flag(registers: &mut RegisterFile, field: &crate::soc::FieldSpec, set: bool) {
let status = registers.get(Register::Sta);
registers.set(Register::Sta, field.set(status, u64::from(set)));
}
fn standing(registers: &RegisterFile, field: &crate::soc::FieldSpec) -> bool {
field.is_set(registers.get(Register::Sta))
}
}
impl Sdio {
/// Sends what `SDIO_ARG` and `SDIO_CMD` hold, 31.9.3 and 31.9.4.
fn send(
&mut self,
written: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if !cmd::CPSMEN.is_set(written) || !Self::powered(registers) {
return;
}
let index = u8::try_from(cmd::CMDINDEX.get(written)).unwrap_or(0);
let argument = u32::try_from(registers.get(Register::Arg) & u64::from(u32::MAX))
.expect("masked to a word");
let wants = cmd::WAITRESP.get(written);
// A command is a transaction with the card, and the first word of its response is the first
// the card answers.
let answer = self.card.as_mut().map_or(Answer::None, |place| {
place.begin();
let answer = place.answering().map_or(Answer::None, |card| {
card.command(index, argument, &mut Timeline::new(context))
});
place.release();
match answer {
Answer::None => Answer::None,
Answer::Short(value) => Answer::Short(place.answered(value)),
Answer::Long([first, second, third, fourth]) => {
Answer::Long([place.answered(first), second, third, fourth])
}
}
});
match (wants, answer) {
(WAIT_NO_RESPONSE | WAIT_NO_RESPONSE_TOO, _) => {
Self::flag(registers, sta::CMDSENT, true);
}
(_, Answer::None) => Self::flag(registers, sta::CTIMEOUT, true),
(WAIT_SHORT, Answer::Short(value)) => {
registers.set(Register::Resp1, u64::from(value));
registers.set(Register::Respcmd, u64::from(index));
Self::flag(registers, sta::CMDREND, true);
// ES0206 rev 24 2.4.2: the check is calculated even where the response carries none, so a
// short answer with all ones where the check would be leaves `CCRCFAIL` standing. Table 175
// gives `R3` exactly that, and `ACMD41` answers `R3`.
if index_answers_without_a_check(index) {
Self::flag(registers, sta::CCRCFAIL, true);
}
}
(WAIT_LONG, Answer::Long(words)) => {
for (register, word) in [
Register::Resp1,
Register::Resp2,
Register::Resp3,
Register::Resp4,
]
.into_iter()
.zip(words)
{
registers.set(register, u64::from(word));
}
registers.set(Register::Respcmd, u64::from(index));
Self::flag(registers, sta::CMDREND, true);
}
// A card answering a width the host didn't ask for is one the check doesn't hold for.
_ => Self::flag(registers, sta::CCRCFAIL, true),
}
self.settle(registers, context);
}
/// Starts or stops the data path, 31.9.9.
fn write_control(
&mut self,
written: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if !dctrl::DTEN.is_set(written) {
self.transfer = None;
Self::flag(registers, sta::RXACT, false);
Self::flag(registers, sta::TXACT, false);
return;
}
if self.transfer.is_some() || !Self::powered(registers) {
return;
}
let direction = if dctrl::DTDIR.is_set(written) {
Direction::FromCard
} else {
Direction::ToCard
};
let block = 1_u64 << dctrl::DBLOCKSIZE.get(written);
let length = registers.get(Register::Dlen);
let clock = self.card_clock(registers);
let per_word = u128::from(BYTES_PER_WORD * 8 / Self::lines(registers)) * NANOS_PER_SECOND
/ u128::from(clock);
self.transfer = Some(Transfer {
direction,
block,
started: context.now(),
per_word,
moved: 0,
remaining: length,
buffer: Vec::with_capacity(usize::try_from(block).unwrap_or(0)),
taken: 0,
timeout: u128::from(registers.get(Register::Dtimer)) * NANOS_PER_SECOND
/ u128::from(clock),
since: context.now(),
stalled: false,
});
registers.set(Register::Dcount, length);
Self::flag(
registers,
match direction {
Direction::FromCard => sta::RXACT,
Direction::ToCard => sta::TXACT,
},
true,
);
self.settle(registers, context);
}
/// Moves the bus on to `now`, which is where every flag this model raises comes from.
fn settle(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
self.run(registers, context);
self.refresh_fifo_flags(registers);
self.drive_request(registers, context);
self.drive_last_data(registers, context);
self.raise(registers, context);
self.schedule(context);
}
/// Every word the clock has had time for since the last settle.
fn run(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let now = context.now();
loop {
let Some(transfer) = self.transfer.as_ref() else {
return;
};
if transfer.remaining == 0 {
self.finish(registers);
return;
}
// 31.9.7: the timer is what ends a transfer whose card stopped answering, and it's the
// only thing that does. A card with nothing to give stalls the data path rather than
// faulting it.
if transfer.stalled {
if u128::from(now.0 - transfer.since.0) >= transfer.timeout {
Self::flag(registers, sta::DTIMEOUT, true);
self.transfer = None;
Self::flag(registers, sta::RXACT, false);
Self::flag(registers, sta::TXACT, false);
}
return;
}
let due = transfer.started.0
+ u64::try_from((u128::from(transfer.moved) + 1) * transfer.per_word)
.unwrap_or(u64::MAX);
if due > now.0 {
return;
}
if !self.move_one_word(registers, now) {
return;
}
}
}
/// One word across the bus, answering whether the next one may follow.
fn move_one_word(&mut self, registers: &mut RegisterFile, now: VirtualNanos) -> bool {
let Some(transfer) = self.transfer.as_mut() else {
return false;
};
match transfer.direction {
Direction::FromCard => {
if transfer.taken == transfer.buffer.len() {
let mut block = vec![0; usize::try_from(transfer.block).unwrap_or(0)];
let outcome = self
.card
.as_mut()
.and_then(Place::answering)
.map_or(Outcome::Ended, |card| card.read(&mut block));
match outcome {
Outcome::Ended => {
transfer.stalled = true;
return false;
}
Outcome::Corrupt => Self::flag(registers, sta::DCRCFAIL, true),
Outcome::Whole => {}
}
transfer.buffer = block;
transfer.taken = 0;
}
if self.fifo.len() == FIFO_WORDS {
// 31.9.11: nothing drained it and the next word has nowhere to go.
Self::flag(registers, sta::RXOVERR, true);
self.transfer = None;
Self::flag(registers, sta::RXACT, false);
return false;
}
let mut word = [0_u8; 4];
for byte in &mut word {
*byte = transfer.buffer.get(transfer.taken).copied().unwrap_or(0);
transfer.taken += 1;
}
let word = u32::from_le_bytes(word);
self.fifo.push_back(
self.card
.as_mut()
.map_or(word, |place| place.answered(word)),
);
}
Direction::ToCard => {
let Some(word) = self.fifo.pop_front() else {
// 31.9.11: the bus wanted a word and the host had not put one there.
Self::flag(registers, sta::TXUNDERR, true);
self.transfer = None;
Self::flag(registers, sta::TXACT, false);
return false;
};
transfer.buffer.extend_from_slice(&word.to_le_bytes());
if u64::try_from(transfer.buffer.len()).unwrap_or(0) >= transfer.block {
let block = std::mem::take(&mut transfer.buffer);
let outcome = self
.card
.as_mut()
.and_then(Place::answering)
.map_or(Outcome::Ended, |card| card.write(&block));
match outcome {
Outcome::Ended => {
transfer.stalled = true;
return false;
}
Outcome::Corrupt => Self::flag(registers, sta::DCRCFAIL, true),
Outcome::Whole => {}
}
Self::flag(registers, sta::DBCKEND, true);
transfer.since = now;
}
}
}
let Some(transfer) = self.transfer.as_mut() else {
return false;
};
transfer.moved += 1;
transfer.remaining = transfer.remaining.saturating_sub(BYTES_PER_WORD);
// 31.9.11 `DBCKEND` on a read: the block is done when its last word has crossed.
if transfer.direction == Direction::FromCard && transfer.taken == transfer.buffer.len() {
Self::flag(registers, sta::DBCKEND, true);
transfer.since = now;
}
registers.set(Register::Dcount, transfer.remaining);
true
}
/// The count reached zero, 31.9.11.
fn finish(&mut self, registers: &mut RegisterFile) {
self.received_last = self
.transfer
.take()
.is_some_and(|transfer| transfer.direction == Direction::FromCard);
Self::flag(registers, sta::RXACT, false);
Self::flag(registers, sta::TXACT, false);
Self::flag(registers, sta::DATAEND, true);
}
/// The eight flags of 31.9.11 that follow what is in the FIFO, and `FIFOCNT` of 31.9.14.
fn refresh_fifo_flags(&mut self, registers: &mut RegisterFile) {
let held = self.fifo.len();
let room = FIFO_WORDS - held;
Self::flag(registers, sta::RXFIFOE, held == 0);
Self::flag(registers, sta::RXFIFOF, held == FIFO_WORDS);
Self::flag(registers, sta::RXFIFOHF, held >= HALF);
Self::flag(registers, sta::RXDAVL, held > 0);
Self::flag(registers, sta::TXFIFOE, held == 0);
Self::flag(registers, sta::TXFIFOF, held == FIFO_WORDS);
Self::flag(registers, sta::TXFIFOHE, room >= HALF);
Self::flag(registers, sta::TXDAVL, held > 0);
// 31.9.14: "the remaining number of words to be written to or read from the FIFO", loaded
// from `SDIO_DLEN` when the transfer is enabled, with "the remaining 1 to 3 bytes regarded
// as a word". It's what's left of the transfer, not what's held in the FIFO.
registers.set(
Register::Fifocnt,
self.transfer
.as_ref()
.map_or(0, |transfer| transfer.remaining.div_ceil(BYTES_PER_WORD)),
);
}
/// The level a stream follows, 31.9.9 and section 10.3.2.
fn drive_request(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let armed = dctrl::DMAEN.is_set(registers.get(Register::Dctrl));
let queued = u64::try_from(self.fifo.len()).expect("thirty-two words") * BYTES_PER_WORD;
let level = match self.transfer.as_ref() {
Some(transfer)
if transfer.direction == Direction::FromCard
&& armed
&& Self::standing(registers, sta::RXFIFOHF) =>
{
Level::High
}
// Section 10.3.15: the flow controller asks for no more than the transfer needs, so what
// the FIFO already queues counts against what is left.
Some(transfer)
if transfer.direction == Direction::ToCard
&& armed
&& Self::standing(registers, sta::TXFIFOHE)
&& queued < transfer.remaining =>
{
Level::High
}
// Section 10.3.15's "last external burst or single request": once a receive's count is out,
// what the FIFO still holds below the half mark is asked for word by word.
None if armed
&& self.received_last
&& Self::standing(registers, sta::DATAEND)
&& Self::standing(registers, sta::RXDAVL) =>
{
Level::High
}
_ => Level::Low,
};
if level != self.request {
self.request = level;
context.request(TRANSFER_REQUEST, level);
}
}
/// Section 10.3.15's signal that "the last data are being transferred": `DATAEND`, 31.9.11, as a level.
fn drive_last_data(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let level = Level::from(Self::standing(registers, sta::DATAEND));
if level != self.last_data {
self.last_data = level;
context.request(LAST_DATA_REQUEST, level);
}
}
/// One line, the level of any flag standing with its enable set, 31.9.13.
fn raise(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let standing = registers.get(Register::Sta) & registers.get(Register::Mask);
let level = if standing == 0 {
Level::Low
} else {
Level::High
};
if level != self.interrupt {
self.interrupt = level;
context.interrupt(GLOBAL, level);
}
}
/// The next instant the bus has something to do at.
fn schedule(&mut self, context: &mut DeviceContext<'_>) {
let next = self.transfer.as_ref().map(|transfer| {
if transfer.stalled {
// Nothing will move again, so the only instant left is the one 31.9.7 gives the timer.
return VirtualNanos(
transfer
.since
.0
.saturating_add(u64::try_from(transfer.timeout).unwrap_or(u64::MAX)),
);
}
let due = u64::try_from((u128::from(transfer.moved) + 1) * transfer.per_word)
.unwrap_or(u64::MAX);
VirtualNanos(transfer.started.0.saturating_add(due))
});
if next != self.wakeup.map(|(at, _)| at) {
self.wakeup = next.map(|at| (at, context.schedule(at)));
}
}
}
/// Whether the card's answer to this index carries no check, ES0206 rev 24 2.4.2.
///
/// Table 175 gives `R3` all ones where a check would be, and 31.4.4 has `ACMD41` answer `R3`. The
/// controller can't tell an application command from a standard one, because 31.4.14 puts that
/// knowledge in the card. So what it has is the index, and `41` is the one on this board's path.
fn index_answers_without_a_check(index: u8) -> bool {
index == crate::chip::sd::command::SD_SEND_OP_COND
}
impl Device for Sdio {
type Register = Register;
/// `SDIOCLK` has changed rate. A transfer under way keeps the rate it began at, and the next is
/// timed at this one.
fn on_clock(
&mut self,
hz: u64,
registers: &mut RegisterFile,
_context: &mut DeviceContext<'_>,
) {
self.clock_hz = hz;
// RM0090 rev 22 figure 16: an interface whose enable is clear has no clock, so its data path
// moves nothing and its data timer doesn't count.
// ceiling: a transfer running when the clock stops is dropped, not frozen; upgrade when a
// firmware gates the interface mid-transfer and waits for the rest of it.
if hz == 0 {
self.transfer = None;
Self::flag(registers, sta::RXACT, false);
Self::flag(registers, sta::TXACT, false);
}
}
fn read(
&mut self,
register: Register,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) -> Option<u64> {
self.settle(registers, context);
let answer = match register {
Register::Fifo => {
let word = self.fifo.pop_front().map_or(0, u64::from);
Some(word)
}
_ => None,
};
self.settle(registers, context);
answer
}
/// `FIFO` is the word a read would pop.
fn peek(
&self,
register: Register,
_registers: &RegisterFile,
_now: VirtualNanos,
) -> Option<u64> {
(register == Register::Fifo).then(|| self.fifo.front().map_or(0, |&word| u64::from(word)))
}
fn write(
&mut self,
register: Register,
written: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
self.settle(registers, context);
match register {
Register::Cmd => self.send(written, registers, context),
Register::Dctrl => self.write_control(written, registers, context),
Register::Fifo => {
if self.fifo.len() < FIFO_WORDS {
self.fifo
.push_back(u32::try_from(written & u64::from(u32::MAX)).unwrap_or(0));
}
self.settle(registers, context);
}
Register::Icr => {
// 31.9.12: each flag is cleared by its own bit and by no other.
let status = registers.get(Register::Sta);
let cleared = status & !clears(written);
registers.set(Register::Sta, cleared);
self.settle(registers, context);
}
_ => self.settle(registers, context),
}
}
fn on_event(
&mut self,
event: EventId,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if self.wakeup.is_some_and(|(_, pending)| pending == event) {
self.wakeup = None;
self.settle(registers, context);
}
if let Some(place) = &mut self.card {
place
.chip_mut()
.on_event(event, &mut Timeline::new(context));
}
}
}
/// The status bits a write to `SDIO_ICR` clears, 31.9.12, each named for the flag it clears.
fn clears(written: u64) -> u64 {
let pairs: [(&crate::soc::FieldSpec, &crate::soc::FieldSpec); 11] = [
(icr::CCRCFAILC, sta::CCRCFAIL),
(icr::DCRCFAILC, sta::DCRCFAIL),
(icr::CTIMEOUTC, sta::CTIMEOUT),
(icr::DTIMEOUTC, sta::DTIMEOUT),
(icr::TXUNDERRC, sta::TXUNDERR),
(icr::RXOVERRC, sta::RXOVERR),
(icr::CMDRENDC, sta::CMDREND),
(icr::CMDSENTC, sta::CMDSENT),
(icr::DATAENDC, sta::DATAEND),
(icr::STBITERRC, sta::STBITERR),
(icr::DBCKENDC, sta::DBCKEND),
];
pairs.into_iter().fold(0, |clearing, (clear, flag)| {
if clear.is_set(written) {
clearing | flag.mask()
} else {
clearing
}
})
}