rigless 0.1.0

Virtual hardware-in-the-loop runtime: run unmodified firmware on an emulated MCU with every peripheral, chip model and unit of virtual time in Rust, and a CPU core of its own that translates the firmware's instructions to host code
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
// 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 | Behaviour | 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 for ever | 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.**
//!
//! | Behaviour | 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};
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)
    }

    /// 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, 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);
        }
    }
}

/// 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
        }
    })
}