use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use core::fmt;
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::space::{
AccessConstraints, AddressSpace, MemAttrs, MemOps, MemResult, Region, RegionRef, RequesterId,
};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{LockRank, Mutex};
use crate::core::value::{Endian, Width};
use crate::core::wire::{Level, WireSource};
use crate::dev::sd::card::{Data, Reply, SdCard};
use crate::dev::sd::slots::{self, Slot};
use crate::machine::realize::{BindCtx, Instance};
use crate::machine::validate::{ClassSchema, PortDir, PropSchema};
pub const CLASS_NAME: &str = "stm32.sdmmc";
const STATE_VERSION: u32 = 1;
pub const REGISTER_BYTES: u64 = 0x400;
pub const FIFO_WORDS: usize = 16;
pub const REGISTER_RANK: LockRank = LockRank::new(0x4d00);
const CELL_RANK: LockRank = LockRank::new(0x4f00);
pub mod pin {
pub const IRQ: &str = "irq";
}
const R_POWER: u64 = 0x00;
const R_CLKCR: u64 = 0x04;
const R_ARGR: u64 = 0x08;
const R_CMDR: u64 = 0x0c;
const R_RESPCMDR: u64 = 0x10;
const R_RESP1R: u64 = 0x14;
const R_RESP4R: u64 = 0x20;
const R_DTIMER: u64 = 0x24;
const R_DLENR: u64 = 0x28;
const R_DCTRL: u64 = 0x2c;
const R_DCNTR: u64 = 0x30;
const R_STAR: u64 = 0x34;
const R_ICR: u64 = 0x38;
const R_MASKR: u64 = 0x3c;
const R_ACKTIMER: u64 = 0x40;
const R_IDMACTRLR: u64 = 0x50;
const R_IDMABSIZER: u64 = 0x54;
const R_IDMABASE0R: u64 = 0x58;
const R_IDMABASE1R: u64 = 0x5c;
const R_FIFOR: u64 = 0x80;
const R_FIFOR_END: u64 = R_FIFOR + (FIFO_WORDS as u64) * 4;
const POWER_PWRCTRL: u32 = 0x3;
const POWER_ON: u32 = 0x3;
const POWER_MASK: u32 = 0x1f;
const CLKCR_CLKDIV: u32 = 0x3ff;
const CLKCR_MASK: u32 = 0x003f_93ff;
const CMD_INDEX: u32 = 0x3f;
const CMD_TRANS: u32 = 1 << 6;
const CMD_STOP: u32 = 1 << 7;
const CMD_WAITRESP_SHIFT: u32 = 8;
const CMD_WAITRESP_MASK: u32 = 0x3;
const CMD_CPSMEN: u32 = 1 << 12;
const CMD_MASK: u32 = 0x0001_ffff;
pub const WAITRESP_NONE: u32 = 0b00;
pub const WAITRESP_SHORT: u32 = 0b01;
pub const WAITRESP_SHORT_NOCRC: u32 = 0b10;
pub const WAITRESP_LONG: u32 = 0b11;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Shape {
None,
Short,
Long,
}
impl Shape {
fn of(waitresp: u32) -> Shape {
match waitresp & CMD_WAITRESP_MASK {
WAITRESP_NONE => Shape::None,
WAITRESP_SHORT | WAITRESP_SHORT_NOCRC => Shape::Short,
_ => Shape::Long,
}
}
}
const DCTRL_DTEN: u32 = 1 << 0;
const DCTRL_DTDIR: u32 = 1 << 1;
const DCTRL_DBLOCKSIZE_SHIFT: u32 = 4;
const DCTRL_DBLOCKSIZE_MASK: u32 = 0xf;
const DCTRL_FIFORST: u32 = 1 << 13;
const DCTRL_MASK: u32 = 0x3fff;
const STA_CCRCFAIL: u32 = 1 << 0;
const STA_DCRCFAIL: u32 = 1 << 1;
const STA_CTIMEOUT: u32 = 1 << 2;
const STA_DTIMEOUT: u32 = 1 << 3;
const STA_TXUNDERR: u32 = 1 << 4;
const STA_RXOVERR: u32 = 1 << 5;
const STA_CMDREND: u32 = 1 << 6;
const STA_CMDSENT: u32 = 1 << 7;
const STA_DATAEND: u32 = 1 << 8;
const STA_DHOLD: u32 = 1 << 9;
const STA_DBCKEND: u32 = 1 << 10;
const STA_DABORT: u32 = 1 << 11;
const STA_DPSMACT: u32 = 1 << 12;
const STA_CPSMACT: u32 = 1 << 13;
const STA_TXFIFOHE: u32 = 1 << 14;
const STA_RXFIFOHF: u32 = 1 << 15;
const STA_TXFIFOF: u32 = 1 << 16;
const STA_RXFIFOF: u32 = 1 << 17;
const STA_TXFIFOE: u32 = 1 << 18;
const STA_RXFIFOE: u32 = 1 << 19;
const STA_BUSYD0: u32 = 1 << 20;
const STA_BUSYD0END: u32 = 1 << 21;
const STA_SDIOIT: u32 = 1 << 22;
const STA_ACKFAIL: u32 = 1 << 23;
const STA_ACKTIMEOUT: u32 = 1 << 24;
const STA_VSWEND: u32 = 1 << 25;
const STA_CKSTOP: u32 = 1 << 26;
const STA_IDMATE: u32 = 1 << 27;
const STA_IDMABTC: u32 = 1 << 28;
const ICR_MASK: u32 = STA_CCRCFAIL
| STA_DCRCFAIL
| STA_CTIMEOUT
| STA_DTIMEOUT
| STA_TXUNDERR
| STA_RXOVERR
| STA_CMDREND
| STA_CMDSENT
| STA_DATAEND
| STA_DHOLD
| STA_DBCKEND
| STA_DABORT
| STA_BUSYD0END
| STA_SDIOIT
| STA_ACKFAIL
| STA_ACKTIMEOUT
| STA_VSWEND
| STA_CKSTOP
| STA_IDMATE
| STA_IDMABTC;
const STA_LATCHED: u32 = ICR_MASK;
const STA_DERIVED: u32 = STA_DPSMACT
| STA_CPSMACT
| STA_TXFIFOHE
| STA_RXFIFOHF
| STA_TXFIFOF
| STA_RXFIFOF
| STA_TXFIFOE
| STA_RXFIFOE
| STA_BUSYD0;
const _: () = assert!(STA_LATCHED & STA_DERIVED == 0);
const _: () = assert!(STA_LATCHED | STA_DERIVED == 0x1fff_ffff);
const MASK_MASK: u32 = ICR_MASK | STA_TXFIFOHE | STA_RXFIFOHF | STA_TXFIFOE | STA_RXFIFOE;
const IDMA_EN: u32 = 1 << 0;
const IDMA_BMODE: u32 = 1 << 1;
const IDMA_BACT: u32 = 1 << 2;
const IDMA_WRITABLE: u32 = IDMA_EN | IDMA_BMODE;
const IDMABSIZE_SHIFT: u32 = 5;
const IDMABSIZE_MASK: u32 = 0xff;
const IDMABSIZE_UNIT: u32 = 32;
const DLEN_MASK: u32 = 0x01ff_ffff;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Dpsm {
to_host: bool,
left: u32,
block: u32,
block_left: u32,
started: bool,
}
#[derive(Debug)]
struct Regs {
power: u32,
clkcr: u32,
arg: u32,
cmd: u32,
respcmd: u32,
resp: [u32; 4],
dtimer: u32,
dlen: u32,
dctrl: u32,
acktimer: u32,
idmactrl: u32,
idmabsize: u32,
idmabase: [u32; 2],
sta: u32,
mask: u32,
fifo: VecDeque<u32>,
dpsm: Option<Dpsm>,
}
impl Regs {
fn reset() -> Regs {
Regs {
power: 0,
clkcr: 0,
arg: 0,
cmd: 0,
respcmd: 0,
resp: [0; 4],
dtimer: 0,
dlen: 0,
dctrl: 0,
acktimer: 0,
idmactrl: 0,
idmabsize: 0,
idmabase: [0; 2],
sta: 0,
mask: 0,
fifo: VecDeque::with_capacity(FIFO_WORDS),
dpsm: None,
}
}
fn powered(&self) -> bool {
self.power & POWER_PWRCTRL == POWER_ON
}
fn status(&self, card_busy: bool) -> u32 {
let mut sta = self.sta & STA_LATCHED;
let level = self.fifo.len();
if level == 0 {
sta |= STA_TXFIFOE | STA_RXFIFOE;
}
if level >= FIFO_WORDS {
sta |= STA_TXFIFOF | STA_RXFIFOF;
}
if level >= FIFO_WORDS / 2 {
sta |= STA_RXFIFOHF;
}
if level <= FIFO_WORDS / 2 {
sta |= STA_TXFIFOHE;
}
if self.dpsm.is_some() {
sta |= STA_DPSMACT;
}
if card_busy {
sta |= STA_BUSYD0;
}
sta
}
fn dcount(&self) -> u32 {
self.dpsm.map_or(0, |d| d.left)
}
}
pub struct Sdmmc {
shared: Arc<Shared>,
region: RegionRef,
}
impl fmt::Debug for Sdmmc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Sdmmc")
.field("slot", &self.shared.slot_name)
.finish_non_exhaustive()
}
}
struct Shared {
regs: Mutex<Regs>,
slot: Arc<Slot>,
slot_name: String,
irq: Mutex<Option<WireSource>>,
bus: Mutex<Option<Arc<AddressSpace>>>,
requester: Mutex<RequesterId>,
}
impl fmt::Debug for Shared {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Shared")
.field("slot", &self.slot_name)
.finish_non_exhaustive()
}
}
impl Sdmmc {
pub fn new(props: &Props) -> Result<Sdmmc> {
let mut r = props.reader();
let slot_name = r.or_str("slot", crate::dev::sd::DEFAULT_SLOT)?.to_string();
r.finish()?;
let slot = slots::attach(props, &slot_name)?;
Ok(Sdmmc::with_slot(slot, slot_name))
}
#[must_use]
pub fn with_slot(slot: Arc<Slot>, slot_name: String) -> Sdmmc {
let shared = Arc::new(Shared {
regs: Mutex::with_rank(REGISTER_RANK, Regs::reset()),
slot,
slot_name,
irq: Mutex::with_rank(CELL_RANK, None),
bus: Mutex::with_rank(CELL_RANK, None),
requester: Mutex::with_rank(CELL_RANK, RequesterId::ANONYMOUS),
});
let port = Arc::new(Port {
shared: Arc::clone(&shared),
});
let region: RegionRef = Arc::new(Region::io(
CLASS_NAME,
REGISTER_BYTES,
port as Arc<dyn MemOps>,
));
Sdmmc { shared, region }
}
#[must_use]
pub fn slot(&self) -> &Arc<Slot> {
&self.shared.slot
}
pub fn attach_irq(&self, source: WireSource) {
*self.shared.irq.lock() = Some(source);
self.shared.refresh_irq();
}
pub fn attach_bus(&self, space: Arc<AddressSpace>, requester: RequesterId) {
*self.shared.bus.lock() = Some(space);
*self.shared.requester.lock() = requester;
}
#[must_use]
pub fn status(&self) -> u32 {
let busy = self.shared.card_busy();
self.shared.regs.lock().status(busy)
}
#[must_use]
pub fn clock_divider(&self) -> u32 {
self.shared.regs.lock().clkcr & CLKCR_CLKDIV
}
}
impl Shared {
fn card(&self) -> Option<Arc<SdCard>> {
self.slot.card()
}
fn card_busy(&self) -> bool {
self.card().is_some_and(|c| c.is_busy())
}
fn refresh_irq(&self) {
let asserted = {
let busy = self.card_busy();
let regs = self.regs.lock();
regs.status(busy) & regs.mask & MASK_MASK != 0
};
if let Some(irq) = self.irq.lock().as_ref() {
irq.set(Level::from(asserted));
}
}
fn run_command(&self, regs: &mut Regs, card: Option<&SdCard>) {
let index = (regs.cmd & CMD_INDEX) as u8;
let waitresp = (regs.cmd >> CMD_WAITRESP_SHIFT) & CMD_WAITRESP_MASK;
let arg = regs.arg;
if !regs.powered() {
regs.sta |= STA_CTIMEOUT;
return;
}
let Some(card) = card else {
regs.sta |= STA_CTIMEOUT;
return;
};
if regs.cmd & CMD_STOP != 0 && regs.dpsm.is_some() {
Self::finish(regs);
regs.fifo.clear();
regs.sta |= STA_DABORT;
}
let reply = card.command(index, arg);
match (Shape::of(waitresp), reply) {
(Shape::None, _) => {
regs.sta |= STA_CMDSENT;
}
(_, Reply::None) => regs.sta |= STA_CTIMEOUT,
(Shape::Long, Reply::Long(words)) => {
regs.respcmd = CMD_INDEX;
regs.resp = words;
regs.sta |= STA_CMDREND;
}
(Shape::Long, Reply::Short { .. }) => {
regs.sta |= STA_CTIMEOUT;
}
(Shape::Short, Reply::Short { index, value, .. }) => {
regs.respcmd = u32::from(index) & CMD_INDEX;
regs.resp[0] = value;
regs.sta |= STA_CMDREND;
}
(Shape::Short, Reply::Long(_)) => {
regs.sta |= STA_CCRCFAIL;
}
}
if regs.cmd & CMD_TRANS != 0 && regs.sta & STA_CMDREND != 0 {
self.start_data(regs, card);
} else {
self.pump(regs, card);
}
}
fn start_data(&self, regs: &mut Regs, card: &SdCard) {
let len = regs.dlen & DLEN_MASK;
let to_host = regs.dctrl & DCTRL_DTDIR != 0;
let shift = (regs.dctrl >> DCTRL_DBLOCKSIZE_SHIFT) & DCTRL_DBLOCKSIZE_MASK;
let block = 1u32 << shift.min(14);
if len == 0 {
Self::finish(regs);
return;
}
regs.fifo.clear();
regs.dpsm = Some(Dpsm {
to_host,
left: len,
block,
block_left: block.min(len),
started: false,
});
self.pump(regs, card);
}
fn pump(&self, regs: &mut Regs, card: &SdCard) {
let Some(dpsm) = regs.dpsm else { return };
if regs.idmactrl & IDMA_EN != 0 {
self.run_idma(regs, card);
} else if dpsm.to_host {
self.fill_fifo(regs, card);
} else {
self.drain_fifo(regs, card);
}
}
fn fill_fifo(&self, regs: &mut Regs, card: &SdCard) {
while regs.fifo.len() < FIFO_WORDS {
let Some(dpsm) = regs.dpsm else { return };
if dpsm.left == 0 {
return;
}
let run = dpsm.left.min(4) as usize;
let mut word = [0u8; 4];
if card.read_data(&mut word[..run]) == Data::Ended {
Self::stalled(regs);
return;
}
regs.fifo.push_back(u32::from_le_bytes(word));
Self::advance(regs, run as u32);
}
}
fn drain_fifo(&self, regs: &mut Regs, card: &SdCard) {
while !regs.fifo.is_empty() {
let Some(dpsm) = regs.dpsm else { return };
if dpsm.left == 0 {
return;
}
let word = regs.fifo.pop_front().expect("not empty");
let run = dpsm.left.min(4) as usize;
if card.write_data(&word.to_le_bytes()[..run]) == Data::Ended {
Self::stalled(regs);
return;
}
Self::advance(regs, run as u32);
}
}
fn finish(regs: &mut Regs) {
regs.dpsm = None;
regs.dctrl &= !DCTRL_DTEN;
}
fn stalled(regs: &mut Regs) {
if regs.dpsm.is_some_and(|d| d.started) {
Self::finish(regs);
regs.sta |= STA_DTIMEOUT;
}
}
fn advance(regs: &mut Regs, moved: u32) {
let Some(dpsm) = regs.dpsm.as_mut() else {
return;
};
dpsm.started = true;
dpsm.left -= moved;
dpsm.block_left -= moved.min(dpsm.block_left);
if dpsm.block_left == 0 {
regs.sta |= STA_DBCKEND;
let block = dpsm.block;
dpsm.block_left = block.min(dpsm.left);
}
if dpsm.left == 0 {
regs.sta |= STA_DATAEND;
Self::finish(regs);
}
}
fn run_idma(&self, regs: &mut Regs, card: &SdCard) {
let Some(dpsm) = regs.dpsm else { return };
let space = self.bus.lock().clone();
let Some(space) = space else {
Self::finish(regs);
regs.sta |= STA_IDMATE;
return;
};
let attrs = MemAttrs {
requester: *self.requester.lock(),
privileged: true,
..MemAttrs::DEFAULT
};
let double = regs.idmactrl & IDMA_BMODE != 0;
let buffer_bytes = ((regs.idmabsize >> IDMABSIZE_SHIFT) & IDMABSIZE_MASK) * IDMABSIZE_UNIT;
if double && buffer_bytes == 0 {
Self::finish(regs);
regs.sta |= STA_IDMATE;
return;
}
let mut which = usize::from(regs.idmactrl & IDMA_BACT != 0);
let mut at = regs.idmabase[if double { which } else { 0 }];
let mut in_buffer = 0u32;
let chunk = dpsm.block.min(dpsm.left).max(1) as usize;
let mut buf = alloc::vec![0u8; chunk];
while regs.dpsm.is_some_and(|d| d.left > 0) {
let dpsm = regs.dpsm.expect("still running");
let mut run = (dpsm.left as usize).min(chunk);
if double {
run = run.min((buffer_bytes - in_buffer) as usize);
}
let slice = &mut buf[..run];
let (card_ok, space_ok) = if dpsm.to_host {
let card_ok = card.read_data(slice) != Data::Ended;
let space_ok = !card_ok || space.write_bytes(u64::from(at), slice, attrs).is_ok();
(card_ok, space_ok)
} else {
let space_ok = space.read_bytes(u64::from(at), slice, attrs).is_ok();
let card_ok = !space_ok || card.write_data(slice) != Data::Ended;
(card_ok, space_ok)
};
if !card_ok {
Self::stalled(regs);
break;
}
if !space_ok {
Self::finish(regs);
regs.sta |= STA_IDMATE;
break;
}
at += run as u32;
in_buffer += run as u32;
Self::advance(regs, run as u32);
if double && in_buffer == buffer_bytes {
regs.sta |= STA_IDMABTC;
which ^= 1;
at = regs.idmabase[which];
in_buffer = 0;
}
}
if double {
regs.idmactrl = (regs.idmactrl & !IDMA_BACT) | ((which as u32) << 2);
}
}
fn read_register(&self, offset: u64, debug: bool) -> u32 {
if (R_FIFOR..R_FIFOR_END).contains(&offset) {
return self.read_fifo(debug);
}
let busy = self.card_busy();
let regs = self.regs.lock();
match offset {
R_POWER => regs.power,
R_CLKCR => regs.clkcr,
R_ARGR => regs.arg,
R_CMDR => regs.cmd,
R_RESPCMDR => regs.respcmd,
R_RESP1R..=R_RESP4R => regs.resp[((offset - R_RESP1R) / 4) as usize],
R_DTIMER => regs.dtimer,
R_DLENR => regs.dlen,
R_DCTRL => regs.dctrl & !DCTRL_FIFORST,
R_DCNTR => regs.dcount(),
R_STAR => regs.status(busy),
R_ICR => regs.sta & ICR_MASK,
R_MASKR => regs.mask,
R_ACKTIMER => regs.acktimer,
R_IDMACTRLR => regs.idmactrl,
R_IDMABSIZER => regs.idmabsize,
R_IDMABASE0R => regs.idmabase[0],
R_IDMABASE1R => regs.idmabase[1],
_ => 0,
}
}
fn read_fifo(&self, debug: bool) -> u32 {
if debug {
return self.regs.lock().fifo.front().copied().unwrap_or(0);
}
let card = self.card();
let mut regs = self.regs.lock();
let word = regs.fifo.pop_front().unwrap_or(0);
if let Some(card) = card.as_deref() {
self.pump(&mut regs, card);
}
word
}
fn write_register(&self, offset: u64, value: u32) {
if (R_FIFOR..R_FIFOR_END).contains(&offset) {
self.write_fifo(value);
return;
}
let card = self.card();
let mut regs = self.regs.lock();
match offset {
R_POWER => {
let was_on = regs.powered();
regs.power = value & POWER_MASK;
if was_on && !regs.powered() {
if let Some(card) = card.as_deref() {
card.power_cycle();
}
Self::finish(&mut regs);
regs.fifo.clear();
}
}
R_CLKCR => regs.clkcr = value & CLKCR_MASK,
R_ARGR => regs.arg = value,
R_CMDR => {
regs.cmd = value & CMD_MASK;
if value & CMD_CPSMEN != 0 {
self.run_command(&mut regs, card.as_deref());
}
}
R_DTIMER => regs.dtimer = value,
R_DLENR => regs.dlen = value & DLEN_MASK,
R_DCTRL => {
if value & DCTRL_FIFORST != 0 {
regs.fifo.clear();
}
regs.dctrl = value & DCTRL_MASK & !DCTRL_FIFORST;
if value & DCTRL_DTEN != 0 && regs.dpsm.is_none() {
if let Some(card) = card.as_deref() {
self.start_data(&mut regs, card);
} else {
regs.sta |= STA_DTIMEOUT;
}
}
}
R_ICR => regs.sta &= !(value & ICR_MASK),
R_MASKR => regs.mask = value & MASK_MASK,
R_ACKTIMER => regs.acktimer = value,
R_IDMACTRLR => {
regs.idmactrl = (regs.idmactrl & IDMA_BACT) | (value & IDMA_WRITABLE);
}
R_IDMABSIZER => regs.idmabsize = value & (IDMABSIZE_MASK << IDMABSIZE_SHIFT),
R_IDMABASE0R => regs.idmabase[0] = value & !0x3,
R_IDMABASE1R => regs.idmabase[1] = value & !0x3,
_ => {}
}
}
fn write_fifo(&self, value: u32) {
let card = self.card();
let mut regs = self.regs.lock();
if regs.dpsm.is_none_or(|d| d.to_host) {
return;
}
if regs.fifo.len() < FIFO_WORDS {
regs.fifo.push_back(value);
}
if let Some(card) = card.as_deref() {
self.pump(&mut regs, card);
}
}
}
struct Port {
shared: Arc<Shared>,
}
impl fmt::Debug for Port {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Port").finish_non_exhaustive()
}
}
impl MemOps for Port {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
if dst.len() != 4 || !offset.is_multiple_of(4) {
return Err(BusError::BadAccess);
}
let value = self.shared.read_register(offset, attrs.debug);
dst.copy_from_slice(&value.to_le_bytes());
if !attrs.debug {
self.shared.refresh_irq();
}
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
if src.len() != 4 || !offset.is_multiple_of(4) {
return Err(BusError::BadAccess);
}
if attrs.debug {
return Err(BusError::BadAccess);
}
self.shared
.write_register(offset, u32::from_le_bytes([src[0], src[1], src[2], src[3]]));
self.shared.refresh_irq();
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::word(Width::U32, Endian::Little)
}
}
impl Device for Sdmmc {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
{
let mut regs = self.shared.regs.lock();
*regs = Regs::reset();
}
self.shared.refresh_irq();
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let regs = self.shared.regs.lock();
w.write_u32(regs.power)?;
w.write_u32(regs.clkcr)?;
w.write_u32(regs.arg)?;
w.write_u32(regs.cmd)?;
w.write_u32(regs.respcmd)?;
for word in regs.resp {
w.write_u32(word)?;
}
w.write_u32(regs.dtimer)?;
w.write_u32(regs.dlen)?;
w.write_u32(regs.dctrl)?;
w.write_u32(regs.acktimer)?;
w.write_u32(regs.idmactrl)?;
w.write_u32(regs.idmabsize)?;
w.write_u32(regs.idmabase[0])?;
w.write_u32(regs.idmabase[1])?;
w.write_u32(regs.sta & STA_LATCHED)?;
w.write_u32(regs.mask)?;
w.write_seq_len(regs.fifo.len() as u64)?;
for word in ®s.fifo {
w.write_u32(*word)?;
}
match regs.dpsm {
None => w.write_bool(false)?,
Some(d) => {
w.write_bool(true)?;
w.write_bool(d.to_host)?;
w.write_bool(d.started)?;
w.write_u32(d.left)?;
w.write_u32(d.block)?;
w.write_u32(d.block_left)?;
}
}
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let mut regs = Regs::reset();
regs.power = r.read_u32()?;
regs.clkcr = r.read_u32()?;
regs.arg = r.read_u32()?;
regs.cmd = r.read_u32()?;
regs.respcmd = r.read_u32()?;
for slot in &mut regs.resp {
*slot = r.read_u32()?;
}
regs.dtimer = r.read_u32()?;
regs.dlen = r.read_u32()?;
regs.dctrl = r.read_u32()?;
regs.acktimer = r.read_u32()?;
regs.idmactrl = r.read_u32()?;
regs.idmabsize = r.read_u32()?;
regs.idmabase[0] = r.read_u32()?;
regs.idmabase[1] = r.read_u32()?;
regs.sta = r.read_u32()? & STA_LATCHED;
regs.mask = r.read_u32()?;
let words = r.read_seq_len(4)?;
if words > FIFO_WORDS as u64 {
return Err(Error::State(alloc::format!(
"the snapshot holds {words} FIFO word(s) and this controller holds {FIFO_WORDS}"
)));
}
for _ in 0..words {
regs.fifo.push_back(r.read_u32()?);
}
regs.dpsm = if r.read_bool()? {
let to_host = r.read_bool()?;
let started = r.read_bool()?;
let left = r.read_u32()?;
let block = r.read_u32()?;
let block_left = r.read_u32()?;
if block == 0 || !block.is_power_of_two() || block_left > block || left == 0 {
return Err(Error::State(alloc::format!(
"a snapshot transfer of {left} byte(s) in {block}-byte blocks is not one this \
controller can hold"
)));
}
Some(Dpsm {
to_host,
left,
block,
block_left,
started,
})
} else {
None
};
*self.shared.regs.lock() = regs;
self.shared.refresh_irq();
Ok(())
}
fn region(&self, name: &str) -> Option<RegionRef> {
matches!(name, "" | "regs").then(|| Arc::clone(&self.region))
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
if port != pin::IRQ {
return Err(Error::Config {
at: String::from(port),
message: alloc::format!(
"an SDMMC controller drives `{}` and nothing else",
pin::IRQ
),
});
}
self.attach_irq(source);
Ok(())
}
fn announce(&self, _port: &str) {
self.shared.refresh_irq();
}
}
impl Instance for Sdmmc {
fn bind(&self, ctx: &BindCtx<'_>) -> Result<()> {
if let Some(space) = ctx.space() {
*self.shared.bus.lock() = Some(Arc::clone(space));
*self.shared.requester.lock() = ctx.requester();
}
Ok(())
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "the STM32H7 SDMMC host controller: the register block, the FIFO and the internal DMA",
properties: &[PropertySpec {
name: "slot",
kind: ValueKind::Str,
required: false,
summary: "the named card slot this controller drives (default `sd0`)",
}],
construct: |props| Ok(Box::new(Sdmmc::new(props)?)),
};
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| Ok(Arc::new(Sdmmc::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("slot", ValueKind::Str))
.port(pin::IRQ, PortDir::Out)
.region("")
.region("regs")
}
#[cfg(test)]
mod tests;