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