openMSX
SdCard.cc
Go to the documentation of this file.
1 #include "SdCard.hh"
2 #include "DeviceConfig.hh"
3 #include "HD.hh"
4 #include "MSXException.hh"
5 #include "unreachable.hh"
6 #include "endian.hh"
7 #include "serialize.hh"
8 #include "serialize_stl.hh"
9 #include <memory>
10 
11 // TODO:
12 // - replace transferDelayCounter with 0xFF's in responseQueue?
13 // - remove duplication between WRITE and MULTI_WRITE (is it worth it?)
14 // - see TODOs in the code below
15 
16 namespace openmsx {
17 
18 // data response tokens
19 constexpr byte DRT_ACCEPTED = 0x05;
20 constexpr byte DRT_WRITE_ERROR = 0x0D;
21 
22 // start block tokens and stop tran token
23 constexpr byte START_BLOCK_TOKEN = 0xFE;
24 constexpr byte START_BLOCK_TOKEN_MBW = 0xFC;
25 constexpr byte STOP_TRAN_TOKEN = 0xFD;
26 
27 // data error token
28 constexpr byte DATA_ERROR_TOKEN_ERROR = 0x01;
29 constexpr byte DATA_ERROR_TOKEN_OUT_OF_RANGE = 0x08;
30 
31 // responses
32 constexpr byte R1_BUSY = 0x00;
33 constexpr byte R1_IDLE = 0x01; // TODO: why is lots of code checking for this instead of R1_BUSY?
34 constexpr byte R1_ILLEGAL_COMMAND = 0x04;
35 constexpr byte R1_PARAMETER_ERROR = 0x80;
36 
38  : hd(config.getXML() ? std::make_unique<HD>(config) : nullptr)
39  , cmdIdx(0)
40  , transferDelayCounter(0)
41  , mode(COMMAND)
42  , currentSector(0)
43  , currentByteInSector(0)
44 {
45 }
46 
47 SdCard::~SdCard() = default;
48 
49 // helper methods for 'transfer' to avoid duplication
50 byte SdCard::readCurrentByteFromCurrentSector()
51 {
52  byte result = [&] {
53  if (currentByteInSector == -1) {
54  try {
55  hd->readSector(currentSector, sectorBuf);
56  return START_BLOCK_TOKEN;
57  } catch (MSXException&) {
59  }
60  } else {
61  // output next byte from stream
62  return sectorBuf.raw[currentByteInSector];
63  }
64  }();
65  currentByteInSector++;
66  if (currentByteInSector == sizeof(sectorBuf)) {
67  responseQueue.push_back({0x00, 0x00}); // 2 CRC's (dummy)
68  }
69  return result;
70 }
71 
72 byte SdCard::transfer(byte value, bool cs)
73 {
74  if (!hd) return 0xFF; // no card inserted
75 
76  if (cs) {
77  // /CS is true: not for this chip
78  return 0xFF;
79  }
80 
81  // process output
82  byte retval = 0xFF;
83  if (transferDelayCounter > 0) {
84  --transferDelayCounter;
85  } else {
86  if (responseQueue.empty()) {
87  switch (mode) {
88  case READ:
89  retval = readCurrentByteFromCurrentSector();
90  if (currentByteInSector == sizeof(sectorBuf)) {
91  mode = COMMAND;
92  }
93  break;
94  case MULTI_READ:
95  // when there's an error, you probably have to send a CMD12
96  // to go back to the COMMAND mode. This is not
97  // clear in the spec (it's wrongly suggested
98  // for the MULTI_WRITE mode!)
99  if (currentSector >= hd->getNbSectors()) {
100  // data out of range, send data error token
102  } else {
103  retval = readCurrentByteFromCurrentSector();
104  if (currentByteInSector == sizeof(sectorBuf)) {
105  currentSector++;
106  currentByteInSector = -1;
107  }
108  }
109  break;
110  case WRITE:
111  case MULTI_WRITE:
112  // apparently nothing is returned while writing
113  case COMMAND:
114  default:
115  break;
116  }
117  } else {
118  retval = responseQueue.pop_front();
119  }
120  }
121 
122  // process input
123  switch (mode) {
124  case WRITE:
125  // first check for data token
126  if (currentByteInSector == -1) {
127  if (value == START_BLOCK_TOKEN) {
128  currentByteInSector++;
129  }
130  break;
131  }
132  if (currentByteInSector < int(sizeof(sectorBuf))) {
133  sectorBuf.raw[currentByteInSector] = value;
134  }
135  currentByteInSector++;
136  if (currentByteInSector == (sizeof(sectorBuf) + 2)) {
137  byte response = DRT_ACCEPTED;
138  // copy buffer to SD card
139  try {
140  hd->writeSector(currentSector, sectorBuf);
141  } catch (MSXException&) {
142  response = DRT_WRITE_ERROR;
143  }
144  mode = COMMAND;
145  transferDelayCounter = 1;
146  responseQueue.push_back(response);
147  }
148  break;
149  case MULTI_WRITE:
150  // first check for stop or start token
151  if (currentByteInSector == -1) {
152  if (value == STOP_TRAN_TOKEN) {
153  mode = COMMAND;
154  }
155  if (value == START_BLOCK_TOKEN_MBW) {
156  currentByteInSector++;
157  }
158  break;
159  }
160  if (currentByteInSector < int(sizeof(sectorBuf))) {
161  sectorBuf.raw[currentByteInSector] = value;
162  }
163  currentByteInSector++;
164  if (currentByteInSector == (sizeof(sectorBuf) + 2)) {
165  // check if still in valid range
166  byte response = DRT_ACCEPTED;
167  if (currentSector >= hd->getNbSectors()) {
168  response = DRT_WRITE_ERROR;
169  // note: mode is not changed, should be done by
170  // the host with CMD12 (STOP_TRANSMISSION) -
171  // however, this makes no sense, CMD12 is only
172  // for Multiple Block Read!? Wrong in the spec?
173  } else {
174  // copy buffer to SD card
175  try {
176  hd->writeSector(currentSector, sectorBuf);
177  currentByteInSector = -1;
178  currentSector++;
179  } catch (MSXException&) {
180  response = DRT_WRITE_ERROR;
181  // note: mode is not changed, should be
182  // done by the host with CMD12
183  // (STOP_TRANSMISSION) - however, this
184  // makes no sense, CMD12 is only for
185  // Multiple Block Read!? Wrong in the
186  // spec?
187  }
188  }
189  transferDelayCounter = 1;
190  responseQueue.push_back(response);
191  }
192  break;
193  case COMMAND:
194  default:
195  if ((cmdIdx == 0 && (value >> 6) == 1) // command start
196  || cmdIdx > 0) { // command in progress
197  cmdBuf[cmdIdx] = value;
198  ++cmdIdx;
199  if (cmdIdx == 6) {
200  executeCommand();
201  cmdIdx = 0;
202  }
203  }
204  break;
205  }
206 
207  return retval;
208 }
209 
210 void SdCard::executeCommand()
211 {
212  // it takes 2 transfers (2x8 cycles) before a reply
213  // can be given to a command
214  transferDelayCounter = 2;
215  byte command = cmdBuf[0] & 0x3F;
216  switch (command) {
217  case 0: // GO_IDLE_STATE
218  responseQueue.clear();
219  mode = COMMAND;
220  responseQueue.push_back(R1_IDLE);
221  break;
222  case 8: // SEND_IF_COND
223  // conditions are always OK
224  responseQueue.push_back({
225  R1_IDLE, // R1 (OK) SDHC (checked by MegaSD and FUZIX)
226  byte(0x02), // command version
227  byte(0x00), // reserved
228  byte(0x01), // voltage accepted
229  cmdBuf[4]});// check pattern
230  break;
231  case 9:{ // SEND_CSD
232  responseQueue.push_back({
233  R1_BUSY, // OK (ignored on MegaSD code, used in FUZIX)
234  // now follows a CSD version 2.0 (for SDHC)
235  START_BLOCK_TOKEN, // data token
236  byte(0x40), // CSD_STRUCTURE [127:120]
237  byte(0x0E), // (TAAC)
238  byte(0x00), // (NSAC)
239  byte(0x32), // (TRAN_SPEED)
240  byte(0x00), // CCC
241  byte(0x00), // CCC / (READ_BL_LEN)
242  byte(0x00)}); // (RBP)/(WBM)/(RBM)/ DSR_IMP
243  // SD_CARD_SIZE = (C_SIZE + 1) * 512kByte
244  auto c_size = unsigned((hd->getNbSectors() * sizeof(sectorBuf)) / (512 * 1024) - 1);
245  responseQueue.push_back({
246  byte((c_size >> 16) & 0x3F), // C_SIZE 1
247  byte((c_size >> 8) & 0xFF), // C_SIZE 2
248  byte((c_size >> 0) & 0xFF), // C_SIZE 3
249  byte(0x00), // res/(EBE)/(SS1)
250  byte(0x00), // (SS2)/(WGS)
251  byte(0x00), // (WGE)/res/(RF)/(WBL1)
252  byte(0x00), // (WBL2)/(WBP)/res
253  byte(0x00), // (FFG)/COPY/PWP/TWP/(FF)/res
254  byte(0x01)}); // CRC / 1
255  break;}
256  case 10: // SEND_CID
257  responseQueue.push_back({
258  R1_BUSY, // OK (ignored on MegaSD, unused in FUZIX so far)
259  START_BLOCK_TOKEN, // data token
260  byte(0xAA), // CID01 // manuf ID
261  byte('o' ), // CID02 // OEM/App ID 1
262  byte('p' ), // CID03 // OEM/App ID 2
263  byte('e' ), // CID04 // Prod name 1
264  byte('n' ), // CID05 // Prod name 2
265  byte('M' ), // CID06 // Prod name 3
266  byte('S' ), // CID07 // Prod name 4
267  byte('X' ), // CID08 // Prod name 5
268  byte(0x01), // CID09 // Prod Revision
269  byte(0x12), // CID10 // Prod Serial 1
270  byte(0x34), // CID11 // Prod Serial 2
271  byte(0x56), // CID12 // Prod Serial 3
272  byte(0x78), // CID13 // Prod Serial 4
273  byte(0x00), // CID14 // reserved / Y1
274  byte(0xE6), // CID15 // Y2 / M
275  byte(0x01)}); // CID16 // CRC / not used
276  break;
277  case 12: // STOP TRANSMISSION
278  responseQueue.push_back(R1_IDLE); // R1 (OK)
279  mode = COMMAND;
280  break;
281  case 16: // SET_BLOCKLEN
282  responseQueue.push_back(R1_IDLE); // OK, we don't really care
283  break;
284  case 17: // READ_SINGLE_BLOCK
285  case 18: // READ_MULTIPLE_BLOCK
286  case 24: // WRITE_BLOCK
287  case 25: // WRITE_MULTIPLE_BLOCK
288  // SDHC so the address is the sector
289  currentSector = Endian::readB32(&cmdBuf[1]);
290  if (currentSector >= hd->getNbSectors()) {
291  responseQueue.push_back(R1_PARAMETER_ERROR);
292  } else {
293  responseQueue.push_back(R1_BUSY);
294  switch (command) {
295  case 17: mode = READ; break;
296  case 18: mode = MULTI_READ; break;
297  case 24: mode = WRITE; break;
298  case 25: mode = MULTI_WRITE; break;
299  default: UNREACHABLE;
300  }
301  currentByteInSector = -1; // wait for token
302  }
303  break;
304  case 41: // implementation of ACMD 41!!
305  // SD_SEND_OP_COND
306  responseQueue.push_back(R1_BUSY);
307  break;
308  case 55: // APP_CMD
309  // TODO: go to ACMD mode, but not necessary now
310  responseQueue.push_back(R1_IDLE);
311  break;
312  case 58: // READ_OCR
313  responseQueue.push_back({
314  R1_BUSY,// R1 (OK) (ignored on MegaSD, checked in FUZIX)
315  byte(0x40), // OCR Reg part 1 (SDHC: CCS=1)
316  byte(0x00), // OCR Reg part 2
317  byte(0x00), // OCR Reg part 3
318  byte(0x00)}); // OCR Reg part 4
319  break;
320 
321  default:
322  responseQueue.push_back(R1_ILLEGAL_COMMAND);
323  break;
324  }
325 }
326 
327 static std::initializer_list<enum_string<SdCard::Mode>> modeInfo = {
328  { "COMMAND", SdCard::COMMAND },
329  { "READ", SdCard::READ },
330  { "MULTI_READ", SdCard::MULTI_READ },
331  { "WRITE", SdCard::WRITE },
332  { "MULTI_WRITE", SdCard::MULTI_WRITE }
333 };
335 
336 template<typename Archive>
337 void SdCard::serialize(Archive& ar, unsigned /*version*/)
338 {
339  ar.serialize("mode", mode,
340  "cmdBuf", cmdBuf);
341  ar.serialize_blob("sectorBuf", sectorBuf.raw, sizeof(sectorBuf));
342  if (hd) ar.serialize("hd", *hd);
343  ar.serialize("cmdIdx", cmdIdx,
344  "transferDelayCounter", transferDelayCounter,
345  "responseQueue", responseQueue,
346  "currentSector", currentSector,
347  "currentByteInSector", currentByteInSector);
348 }
350 
351 } // namespace openmsx
openmsx::SdCard::MULTI_READ
@ MULTI_READ
Definition: SdCard.hh:29
openmsx::R1_IDLE
constexpr byte R1_IDLE
Definition: SdCard.cc:33
openmsx::DRT_ACCEPTED
constexpr byte DRT_ACCEPTED
Definition: SdCard.cc:19
serialize.hh
openmsx::R1_ILLEGAL_COMMAND
constexpr byte R1_ILLEGAL_COMMAND
Definition: SdCard.cc:34
openmsx::SdCard::~SdCard
~SdCard()
openmsx::DeviceConfig
Definition: DeviceConfig.hh:20
openmsx::SERIALIZE_ENUM
SERIALIZE_ENUM(CassettePlayer::State, stateInfo)
openmsx::START_BLOCK_TOKEN
constexpr byte START_BLOCK_TOKEN
Definition: SdCard.cc:23
cb_queue::empty
bool empty() const
Definition: circular_buffer.hh:385
cb_queue::push_back
void push_back(U &&u)
Definition: circular_buffer.hh:358
cb_queue::clear
void clear()
Definition: circular_buffer.hh:386
SdCard.hh
openmsx::HD
Definition: HD.hh:22
openmsx::byte
uint8_t byte
8 bit unsigned integer
Definition: openmsx.hh:26
openmsx::STOP_TRAN_TOKEN
constexpr byte STOP_TRAN_TOKEN
Definition: SdCard.cc:25
MSXException.hh
openmsx::MSXException
Definition: MSXException.hh:10
openmsx::R1_PARAMETER_ERROR
constexpr byte R1_PARAMETER_ERROR
Definition: SdCard.cc:35
openmsx::DATA_ERROR_TOKEN_OUT_OF_RANGE
constexpr byte DATA_ERROR_TOKEN_OUT_OF_RANGE
Definition: SdCard.cc:29
openmsx::SdCard
Definition: SdCard.hh:15
openmsx::SectorBuffer::raw
byte raw[512]
Definition: DiskImageUtils.hh:91
Endian::readB32
uint32_t readB32(const void *p)
Definition: endian.hh:150
openmsx::DRT_WRITE_ERROR
constexpr byte DRT_WRITE_ERROR
Definition: SdCard.cc:20
UNREACHABLE
#define UNREACHABLE
Definition: unreachable.hh:38
openmsx::R1_BUSY
constexpr byte R1_BUSY
Definition: SdCard.cc:32
cb_queue::pop_front
T pop_front()
Definition: circular_buffer.hh:365
INSTANTIATE_SERIALIZE_METHODS
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:982
openmsx::SCSI::COMMAND
@ COMMAND
Definition: SCSI.hh:105
openmsx::SdCard::READ
@ READ
Definition: SdCard.hh:28
serialize_stl.hh
openmsx::SdCard::transfer
byte transfer(byte value, bool cs)
Definition: SdCard.cc:72
endian.hh
openmsx::DATA_ERROR_TOKEN_ERROR
constexpr byte DATA_ERROR_TOKEN_ERROR
Definition: SdCard.cc:28
openmsx::SdCard::SdCard
SdCard(const DeviceConfig &config)
Definition: SdCard.cc:37
openmsx::SdCard::MULTI_WRITE
@ MULTI_WRITE
Definition: SdCard.hh:31
HD.hh
openmsx::SdCard::WRITE
@ WRITE
Definition: SdCard.hh:30
openmsx::SdCard::COMMAND
@ COMMAND
Definition: SdCard.hh:27
openmsx::SdCard::Mode
Mode
Definition: SdCard.hh:26
unreachable.hh
DeviceConfig.hh
openmsx::START_BLOCK_TOKEN_MBW
constexpr byte START_BLOCK_TOKEN_MBW
Definition: SdCard.cc:24
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5
openmsx::SdCard::serialize
void serialize(Archive &ar, unsigned version)
Definition: SdCard.cc:337