rigless 0.2.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
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The card end of a secure digital bus: the seam a controller calls and a card answers.
//!
//! The bus is command and answer, not bits on a wire. A controller sends an index and an argument
//! and takes back a response, and a transfer moves whole blocks. Modeling the line coding would be
//! a great deal of work that nothing could observe, because a controller doesn't expose a bit
//! either. [`Card`] is the seam, and [`crate::chip::memory_card::MemoryCard`] is a card on it.

use std::any::Any;
use std::fmt;

use crate::chip::Timeline;
use crate::time::{EventId, VirtualNanos};

/// Bytes in a block, the length every driver sets and every transfer moves.
pub const BLOCK: usize = 512;

/// What a card answers a command with.
///
/// A response is the card's whole reply. Which registers the controller puts it in is the
/// controller's business, and depends on what the host asked for.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Answer {
    /// Nothing at all, which is a host waiting until its command times out. It's also a bench with
    /// no card in the slot, and a command a card doesn't implement.
    None,
    /// Forty-eight bits, of which these are the thirty-two the host reads.
    Short(u32),
    /// One hundred and thirty-six bits, most significant word first.
    Long([u32; 4]),
}

/// Whether the card is still doing what the last command asked.
///
/// A block write isn't done when the card answers. It programs, and says so by holding the line the
/// host watches. This is that line.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Busy {
    /// The card is ready for the next command.
    Ready,
    /// The card is programming and will be ready at this instant.
    Until(VirtualNanos),
}

/// What a block transfer did.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Outcome {
    /// The block crossed whole.
    Whole,
    /// The block crossed and its check doesn't hold, which a controller reads as a failed block
    /// rather than as a card that stopped answering.
    Corrupt,
    /// The card has no block to give, or will take no more.
    Ended,
}

/// The card end of a secure digital bus.
///
/// The controller calls this and never learns what's behind it. A card implements it and never
/// learns there's a controller. A bench with nothing in the slot has no card at all, not one that
/// answers nothing, so a firmware sees the part behave as it does with an empty slot. A snapshot
/// keeps a card through its own [`crate::snapshot::Store`].
pub trait Card: crate::snapshot::Store + fmt::Debug + Send + Any {
    /// The card's answer to command `index` carrying `argument`.
    ///
    /// `context` is the controller's, narrowed to the instant of the command and its event queue,
    /// so a card that needs to know the time to answer can ask for it, or schedule an event it hears at [`Card::on_event`]. A write's
    /// programming does.
    fn command(&mut self, index: u8, argument: u32, context: &mut Timeline<'_, '_>) -> Answer;

    /// An event this chip scheduled through the [`Timeline`] it was handed, at its instant. The
    /// controller hands every event of its instance to its card, after its own, so a
    /// card ignores an id it didn't ask for. A silenced card hears its events all the same, since
    /// what it does with time goes on while it's cut off. Nothing, for a card that schedules none.
    fn on_event(&mut self, _event: EventId, _context: &mut Timeline<'_, '_>) {}

    /// The part this chip hangs on restarted at the instant `context` gives, and every event the
    /// chip scheduled through its controller went with the reset, since a chip keeps running
    /// through its part's reset. A chip that keeps time schedules again here. Nothing, for a chip
    /// that schedules none.
    fn restarted(&mut self, _context: &mut Timeline<'_, '_>) {}

    /// Whether the card is still busy with the last command, and until when.
    fn busy(&self) -> Busy {
        Busy::Ready
    }

    /// Fills `block` with the next block of a read the last command started.
    fn read(&mut self, block: &mut [u8]) -> Outcome;

    /// Takes `block` as the next block of a write the last command started.
    fn write(&mut self, block: &[u8]) -> Outcome;

    /// Every command the card was sent, oldest first, as index and argument.
    ///
    /// The card's own record, and the only thing that can say a host's sequence arrived. A model
    /// that never reached its card fills what the controller believes it sent just as happily.
    fn commanded(&self) -> &[(u8, u32)] {
        &[]
    }
}

/// The commands a host sends a memory card on the bus, by their index.
pub mod command {
    /// The software reset, which puts a card back in its idle state.
    pub const GO_IDLE_STATE: u8 = 0;
    /// Every card that's still identifying answers its identification.
    pub const ALL_SEND_CID: u8 = 2;
    /// The card takes the address a host reaches it at from then on.
    pub const SET_RELATIVE_ADDR: u8 = 3;
    /// Selects the card at an address, or deselects it, which every transfer starts with.
    pub const SELECT_DESELECT_CARD: u8 = 7;
    /// The card-specific data, which is where a host reads the capacity.
    pub const SEND_CSD: u8 = 9;
    /// What ends a multiple-block transfer.
    pub const STOP_TRANSMISSION: u8 = 12;
    /// The card status register, which a host polls a write with.
    pub const SEND_STATUS: u8 = 13;
    /// Sets the block length a transfer moves.
    pub const SET_BLOCKLEN: u8 = 16;
    /// One block.
    pub const READ_SINGLE_BLOCK: u8 = 17;
    /// Consecutive blocks until `STOP_TRANSMISSION`.
    pub const READ_MULTIPLE_BLOCK: u8 = 18;
    /// One block.
    pub const WRITE_BLOCK: u8 = 24;
    /// Consecutive blocks until `STOP_TRANSMISSION`.
    pub const WRITE_MULTIPLE_BLOCK: u8 = 25;
    /// The escape: the next command is an application command.
    pub const APP_CMD: u8 = 55;
    /// The bus width, an application command a card takes only once it's selected.
    pub const SET_BUS_WIDTH: u8 = 6;
    /// The operating range a card works in, an application command too, which a host polls until
    /// the card says its power-up is done.
    pub const SD_SEND_OP_COND: u8 = 41;
}