use std::collections::BTreeMap;
use crate::chip::Timeline;
use crate::chip::sd::{Answer, BLOCK, Busy, Card, Outcome, command};
use crate::snapshot::store;
use crate::time::VirtualNanos;
const BLOCK_LENGTH: u32 = 512;
mod status {
use crate::soc::{Access, FieldSpec};
pub(super) const CURRENT_STATE: FieldSpec =
FieldSpec::new("CURRENT_STATE", 9, 4, Access::ReadOnly);
pub(super) const READY_FOR_DATA: FieldSpec =
FieldSpec::new("READY_FOR_DATA", 8, 1, Access::ReadOnly);
pub(super) const APP_CMD: FieldSpec = FieldSpec::new("APP_CMD", 5, 1, Access::ReadOnly);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum State {
Idle = 0,
Ready = 1,
Identification = 2,
Standby = 3,
Transfer = 4,
SendingData = 5,
ReceivingData = 6,
Programming = 7,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct Streaming {
block: usize,
multiple: bool,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct MemoryCard {
capacity: usize,
#[serde(with = "blocks")]
blocks: BTreeMap<usize, Box<[u8; BLOCK]>>,
state: State,
address: u16,
block_length: u32,
wide: bool,
escaped: bool,
streaming: Option<Streaming>,
programming_until: Option<VirtualNanos>,
commanded: Vec<(u8, u32)>,
answer_corrupt: bool,
}
mod blocks {
use std::collections::BTreeMap;
use serde::{Deserialize, Deserializer, Serializer};
use super::BLOCK;
pub(super) fn serialize<S: Serializer>(
blocks: &BTreeMap<usize, Box<[u8; BLOCK]>>,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.collect_map(blocks.iter().map(|(index, block)| (index, &block[..])))
}
pub(super) fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<BTreeMap<usize, Box<[u8; BLOCK]>>, D::Error> {
BTreeMap::<usize, Vec<u8>>::deserialize(deserializer)?
.into_iter()
.map(|(index, block)| {
let read = block.len();
block
.into_boxed_slice()
.try_into()
.map(|block| (index, block))
.map_err(|_short| serde::de::Error::invalid_length(read, &"a block"))
})
.collect()
}
}
const OPERATING_RANGE: u32 = 0x8010_0000;
const PROGRAMMING: VirtualNanos = VirtualNanos(1_000);
impl MemoryCard {
#[must_use]
pub fn empty(blocks: usize) -> Self {
assert!(blocks > 0, "a card has at least one block");
Self {
capacity: blocks,
blocks: BTreeMap::new(),
state: State::Idle,
address: 0,
block_length: BLOCK_LENGTH,
wide: false,
escaped: false,
streaming: None,
programming_until: None,
commanded: Vec::new(),
answer_corrupt: false,
}
}
#[must_use]
pub fn holding(contents: &[u8]) -> Self {
assert!(!contents.is_empty(), "a card has at least one block");
let blocks = contents.len().div_ceil(BLOCK);
let mut card = Self::empty(blocks);
for (index, chunk) in contents.chunks(BLOCK).enumerate() {
card.block_mut(index)[..chunk.len()].copy_from_slice(chunk);
}
card
}
#[must_use]
pub fn formatted(blocks: usize) -> Self {
let mut card = Self::empty(blocks);
for (index, sector) in fat32::volume(blocks) {
*card.block_mut(index) = sector;
}
card
}
fn block_mut(&mut self, index: usize) -> &mut [u8; BLOCK] {
self.blocks
.entry(index)
.or_insert_with(|| Box::new([0; BLOCK]))
}
pub fn corrupt_every_block(&mut self, corrupt: bool) {
self.answer_corrupt = corrupt;
}
#[must_use]
pub fn state(&self) -> State {
self.state
}
#[must_use]
pub fn wide(&self) -> bool {
self.wide
}
fn status(&self) -> u32 {
let status = status::CURRENT_STATE.set(0, self.state as u64);
let status = status::READY_FOR_DATA.set(status, 1);
let status = status::APP_CMD.set(status, u64::from(self.escaped));
u32::try_from(status).expect("the fields are inside 32 bits")
}
fn card_specific(&self) -> [u32; 4] {
let half_megabytes =
u32::try_from(self.capacity / (512 * 1024 / BLOCK)).unwrap_or(u32::MAX);
let size = half_megabytes.saturating_sub(1);
[
0x400E_0032,
0x5B59_0000 | (size >> 16),
(size & 0xFFFF) << 16,
0x0A40_0001,
]
}
fn offset(&self, argument: u32) -> Option<usize> {
let block = argument as usize;
(block < self.capacity).then_some(block)
}
}
store!(MemoryCard);
impl Card for MemoryCard {
fn command(&mut self, index: u8, argument: u32, context: &mut Timeline<'_, '_>) -> Answer {
self.commanded.push((index, argument));
if self
.programming_until
.is_some_and(|until| context.now() >= until)
{
self.programming_until = None;
if self.state == State::Programming {
self.state = State::Transfer;
}
}
let escaped = std::mem::take(&mut self.escaped);
if escaped {
return self.application_command(index, argument);
}
match index {
command::GO_IDLE_STATE => {
self.state = State::Idle;
self.streaming = None;
self.escaped = false;
Answer::None
}
command::ALL_SEND_CID if self.state == State::Ready => {
self.state = State::Identification;
Answer::Long([0x0353_4453, 0x4330_3847, 0x3080_1234, 0x0115_8000])
}
command::SET_RELATIVE_ADDR if self.state == State::Identification => {
self.address = 0x0001;
self.state = State::Standby;
Answer::Short(u32::from(self.address) << 16)
}
command::SEND_CSD if self.state == State::Standby => Answer::Long(self.card_specific()),
command::SELECT_DESELECT_CARD => {
self.state = if argument >> 16 == u32::from(self.address) && self.address != 0 {
State::Transfer
} else {
State::Standby
};
Answer::Short(self.status())
}
command::SET_BLOCKLEN if self.state == State::Transfer => {
self.block_length = argument;
Answer::Short(self.status())
}
command::APP_CMD => {
self.escaped = true;
Answer::Short(self.status())
}
command::READ_SINGLE_BLOCK | command::READ_MULTIPLE_BLOCK
if self.state == State::Transfer =>
{
let Some(block) = self.offset(argument) else {
return Answer::Short(self.status());
};
self.streaming = Some(Streaming {
block,
multiple: index == command::READ_MULTIPLE_BLOCK,
});
self.state = State::SendingData;
Answer::Short(self.status())
}
command::WRITE_BLOCK | command::WRITE_MULTIPLE_BLOCK
if self.state == State::Transfer =>
{
let Some(block) = self.offset(argument) else {
return Answer::Short(self.status());
};
self.streaming = Some(Streaming {
block,
multiple: index == command::WRITE_MULTIPLE_BLOCK,
});
self.state = State::ReceivingData;
Answer::Short(self.status())
}
command::STOP_TRANSMISSION => {
self.streaming = None;
self.state = match self.state {
State::ReceivingData => {
self.programming_until =
Some(VirtualNanos(context.now().0 + PROGRAMMING.0));
State::Programming
}
_ => State::Transfer,
};
Answer::Short(self.status())
}
_ => Answer::Short(self.status()),
}
}
fn busy(&self) -> Busy {
self.programming_until.map_or(Busy::Ready, Busy::Until)
}
fn read(&mut self, block: &mut [u8]) -> Outcome {
let Some(streaming) = self.streaming else {
return Outcome::Ended;
};
if streaming.block >= self.capacity || block.len() > BLOCK {
self.streaming = None;
self.state = State::Transfer;
return Outcome::Ended;
}
let stored = self.stored(streaming.block);
block.copy_from_slice(&stored[..block.len()]);
if streaming.multiple {
self.streaming = Some(Streaming {
block: streaming.block + 1,
..streaming
});
} else {
self.streaming = None;
self.state = State::Transfer;
}
if self.answer_corrupt {
Outcome::Corrupt
} else {
Outcome::Whole
}
}
fn write(&mut self, block: &[u8]) -> Outcome {
let Some(streaming) = self.streaming else {
return Outcome::Ended;
};
if streaming.block >= self.capacity || block.len() > BLOCK {
self.streaming = None;
self.state = State::Transfer;
return Outcome::Ended;
}
self.block_mut(streaming.block)[..block.len()].copy_from_slice(block);
if streaming.multiple {
self.streaming = Some(Streaming {
block: streaming.block + 1,
..streaming
});
} else {
self.streaming = None;
self.state = State::Transfer;
}
Outcome::Whole
}
fn commanded(&self) -> &[(u8, u32)] {
&self.commanded
}
}
impl MemoryCard {
#[must_use]
pub fn stored(&self, block: usize) -> [u8; BLOCK] {
self.blocks.get(&block).map_or([0; BLOCK], |held| **held)
}
fn application_command(&mut self, index: u8, argument: u32) -> Answer {
match index {
command::SD_SEND_OP_COND => {
if self.state == State::Idle {
self.state = State::Ready;
}
Answer::Short(OPERATING_RANGE)
}
command::SET_BUS_WIDTH if self.state == State::Transfer => {
self.wide = argument & 0b11 == 0b10;
Answer::Short(self.status())
}
_ => self.command_after_escape(index, argument),
}
}
fn command_after_escape(&mut self, index: u8, argument: u32) -> Answer {
match index {
command::SELECT_DESELECT_CARD => {
self.state = if argument >> 16 == u32::from(self.address) && self.address != 0 {
State::Transfer
} else {
State::Standby
};
Answer::Short(self.status())
}
_ => Answer::Short(self.status()),
}
}
}
mod fat32 {
use super::BLOCK;
const BS_JMP_BOOT: usize = 0;
const BPB_BYTS_PER_SEC: usize = 11;
const BPB_SEC_PER_CLUS: usize = 13;
const BPB_RSVD_SEC_CNT: usize = 14;
const BPB_NUM_FATS: usize = 16;
const BPB_MEDIA: usize = 21;
const BPB_SEC_PER_TRK: usize = 24;
const BPB_NUM_HEADS: usize = 26;
const BPB_TOT_SEC32: usize = 32;
const BPB_FAT_SZ32: usize = 36;
const BPB_ROOT_CLUS32: usize = 44;
const BPB_FS_INFO32: usize = 48;
const BPB_BK_BOOT_SEC32: usize = 50;
const BS_DRV_NUM32: usize = 64;
const BS_BOOT_SIG32: usize = 66;
const BS_VOL_ID32: usize = 67;
const BS_VOL_LAB32: usize = 71;
const BS_55AA: usize = 510;
const FSI_LEAD_SIG: usize = 0;
const FSI_STRUC_SIG: usize = 484;
const FSI_FREE_COUNT: usize = 488;
const FSI_NXT_FREE: usize = 492;
const JUMP_AND_OEM: &[u8; 11] = b"\xEB\xFE\x90MSDOS5.0";
const RESERVED_SECTORS: u16 = 32;
const FATS: u8 = 2;
const MEDIA: u8 = 0xF8;
const SECTORS_PER_TRACK: u16 = 63;
const HEADS: u16 = 255;
const ROOT_CLUSTER: u32 = 2;
const FSINFO_SECTOR: u16 = 1;
const BACKUP_BOOT_SECTOR: u16 = 6;
const BACKUP_FSINFO_SECTOR: usize = 7;
const DRIVE_NUMBER: u8 = 0x80;
const EXTENDED_BOOT_SIGNATURE: u8 = 0x29;
const LABEL_AND_TYPE: &[u8; 19] = b"NO NAME FAT32 ";
const SIGNATURE: u16 = 0xAA55;
const FSI_LEAD: u32 = 0x4161_5252;
const FSI_STRUC: u32 = 0x6141_7272;
const FAT_0: u32 = 0xFFFF_FFF8;
const FAT_1: u32 = 0xFFFF_FFFF;
const FAT_ROOT: u32 = 0x0FFF_FFFF;
const SECTORS_PER_CLUSTER: u8 = 64;
const MIN_FAT32_CLUSTERS: usize = 0xFFF5;
const VOLUME_SERIAL: u32 = 0x5249_474C;
pub(super) fn volume(sectors: usize) -> Vec<(usize, [u8; BLOCK])> {
let per_cluster = usize::from(SECTORS_PER_CLUSTER);
let clusters_at_most = sectors / per_cluster;
let fat_sectors = (clusters_at_most * 4 + 8).div_ceil(BLOCK);
let reserved = usize::from(RESERVED_SECTORS);
let data = reserved + usize::from(FATS) * fat_sectors;
let clusters = (sectors - data) / per_cluster;
assert!(clusters > MIN_FAT32_CLUSTERS, "too few clusters for FAT32");
let mut boot = [0; BLOCK];
boot[..JUMP_AND_OEM.len()].copy_from_slice(JUMP_AND_OEM);
word(
&mut boot,
BPB_BYTS_PER_SEC,
u16::try_from(BLOCK).expect("a sector's size"),
);
boot[BPB_SEC_PER_CLUS] = SECTORS_PER_CLUSTER;
word(&mut boot, BPB_RSVD_SEC_CNT, RESERVED_SECTORS);
boot[BPB_NUM_FATS] = FATS;
dword(&mut boot, BPB_TOT_SEC32, sector_count(sectors));
boot[BPB_MEDIA] = MEDIA;
word(&mut boot, BPB_SEC_PER_TRK, SECTORS_PER_TRACK);
word(&mut boot, BPB_NUM_HEADS, HEADS);
dword(&mut boot, BS_VOL_ID32, VOLUME_SERIAL);
dword(&mut boot, BPB_FAT_SZ32, sector_count(fat_sectors));
dword(&mut boot, BPB_ROOT_CLUS32, ROOT_CLUSTER);
word(&mut boot, BPB_FS_INFO32, FSINFO_SECTOR);
word(&mut boot, BPB_BK_BOOT_SEC32, BACKUP_BOOT_SECTOR);
boot[BS_DRV_NUM32] = DRIVE_NUMBER;
boot[BS_BOOT_SIG32] = EXTENDED_BOOT_SIGNATURE;
boot[BS_VOL_LAB32..BS_VOL_LAB32 + LABEL_AND_TYPE.len()].copy_from_slice(LABEL_AND_TYPE);
word(&mut boot, BS_55AA, SIGNATURE);
let mut info = [0; BLOCK];
dword(&mut info, FSI_LEAD_SIG, FSI_LEAD);
dword(&mut info, FSI_STRUC_SIG, FSI_STRUC);
dword(&mut info, FSI_FREE_COUNT, sector_count(clusters - 1));
dword(&mut info, FSI_NXT_FREE, ROOT_CLUSTER);
word(&mut info, BS_55AA, SIGNATURE);
let mut fat = [0; BLOCK];
dword(&mut fat, 0, FAT_0);
dword(&mut fat, 4, FAT_1);
dword(&mut fat, 8, FAT_ROOT);
vec![
(BS_JMP_BOOT, boot),
(usize::from(BACKUP_BOOT_SECTOR), boot),
(usize::from(FSINFO_SECTOR), info),
(BACKUP_FSINFO_SECTOR, info),
(reserved, fat),
(reserved + fat_sectors, fat),
]
}
fn sector_count(count: usize) -> u32 {
u32::try_from(count).expect("a FAT32 volume counts in thirty-two bits")
}
fn word(sector: &mut [u8; BLOCK], at: usize, value: u16) {
sector[at..at + 2].copy_from_slice(&value.to_le_bytes());
}
fn dword(sector: &mut [u8; BLOCK], at: usize, value: u32) {
sector[at..at + 4].copy_from_slice(&value.to_le_bytes());
}
}