use std::collections::BTreeMap;
use std::fmt;
use crate::device::DeviceContext;
use crate::time::VirtualNanos;
pub const BLOCK: usize = 512;
const BLOCK_LENGTH: u32 = 512;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Answer {
None,
Short(u32),
Long([u32; 4]),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Busy {
Ready,
Until(VirtualNanos),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Outcome {
Whole,
Corrupt,
Ended,
}
pub trait Card: fmt::Debug + Send {
fn command(&mut self, index: u8, argument: u32, context: &DeviceContext<'_>) -> Answer;
fn busy(&self) -> Busy {
Busy::Ready
}
fn read(&mut self, block: &mut [u8]) -> Outcome;
fn write(&mut self, block: &[u8]) -> Outcome;
fn commanded(&self) -> &[(u8, u32)] {
&[]
}
fn stored(&self, block: usize) -> [u8; BLOCK] {
let _ = block;
[0; BLOCK]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
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)]
struct Streaming {
block: usize,
multiple: bool,
}
#[derive(Debug)]
pub struct MemoryCard {
capacity: usize,
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,
}
pub mod command {
pub const GO_IDLE_STATE: u8 = 0;
pub const ALL_SEND_CID: u8 = 2;
pub const SET_RELATIVE_ADDR: u8 = 3;
pub const SELECT_DESELECT_CARD: u8 = 7;
pub const SEND_CSD: u8 = 9;
pub const STOP_TRANSMISSION: u8 = 12;
pub const SEND_STATUS: u8 = 13;
pub const SET_BLOCKLEN: u8 = 16;
pub const READ_SINGLE_BLOCK: u8 = 17;
pub const READ_MULTIPLE_BLOCK: u8 = 18;
pub const WRITE_BLOCK: u8 = 24;
pub const WRITE_MULTIPLE_BLOCK: u8 = 25;
pub const APP_CMD: u8 = 55;
pub const SET_BUS_WIDTH: u8 = 6;
pub const SD_SEND_OP_COND: u8 = 41;
}
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 ready = 1 << 8;
let escaped = u32::from(self.escaped) << 5;
((self.state as u32) << 9) | ready | escaped
}
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)
}
}
impl Card for MemoryCard {
fn command(&mut self, index: u8, argument: u32, context: &DeviceContext<'_>) -> 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
}
fn stored(&self, block: usize) -> [u8; BLOCK] {
self.blocks.get(&block).map_or([0; BLOCK], |held| **held)
}
}
impl MemoryCard {
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());
}
}