OXYGENHUB
◆ main openmsx / src / memory / SdCard.cc
10.0 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "SdCard.hh"
2
3
#include "DeviceConfig.hh"
4
#include "HD.hh"
5
#include "MSXException.hh"
6
#include "serialize.hh"
7
#include "serialize_stl.hh"
8
9
#include "endian.hh"
10
#include "narrow.hh"
11
#include "unreachable.hh"
12
13
#include <memory>
14
15
// TODO:
16
// - replace transferDelayCounter with 0xFF's in responseQueue?
17
// - remove duplication between WRITE and MULTI_WRITE (is it worth it?)
18
// - see TODOs in the code below
19
20
namespace openmsx {
21
22
// data response tokens
23
static constexpr uint8_t DRT_ACCEPTED    = 0x05;
24
static constexpr uint8_t DRT_WRITE_ERROR = 0x0D;
25
26
// start block tokens and stop tran token
27
static constexpr uint8_t START_BLOCK_TOKEN     = 0xFE;
28
static constexpr uint8_t START_BLOCK_TOKEN_MBW = 0xFC;
29
static constexpr uint8_t STOP_TRAN_TOKEN       = 0xFD;
30
31
// data error token
32
static constexpr uint8_t DATA_ERROR_TOKEN_ERROR        = 0x01;
33
static constexpr uint8_t DATA_ERROR_TOKEN_OUT_OF_RANGE = 0x08;
34
35
// responses
36
static constexpr uint8_t R1_BUSY            = 0x00;
37
static constexpr uint8_t R1_IDLE            = 0x01; // TODO: why is lots of code checking for this instead of R1_BUSY?
38
static constexpr uint8_t R1_ILLEGAL_COMMAND = 0x04;
39
static constexpr uint8_t R1_PARAMETER_ERROR = 0x80;
40
41
SdCard::SdCard(const DeviceConfig& config)
42
	: hd(config.getXML() ? std::make_unique<HD>(config) : nullptr)
43
{
44
}
45
46
SdCard::~SdCard() = default;
47
48
// helper methods for 'transfer' to avoid duplication
49
uint8_t SdCard::readCurrentByteFromCurrentSector()
50
{
51
	uint8_t result = [&] {
52
		if (currentByteInSector == -1) {
53
			try {
54
				hd->readSector(currentSector, sectorBuf);
55
				return START_BLOCK_TOKEN;
56
			} catch (MSXException&) {
57
				return DATA_ERROR_TOKEN_ERROR;
58
			}
59
		} else {
60
			// output next byte from stream
61
			return sectorBuf.raw[currentByteInSector];
62
		}
63
	}();
64
	currentByteInSector++;
65
	if (currentByteInSector == sizeof(sectorBuf)) {
66
		responseQueue.push_back({uint8_t(0x00), uint8_t(0x00)}); // 2 CRC's (dummy)
67
	}
68
	return result;
69
}
70
71
uint8_t SdCard::transfer(uint8_t value, bool cs)
72
{
73
	if (!hd) return 0xFF; // no card inserted
74
75
	if (cs) {
76
		// /CS is true: not for this chip
77
		return 0xFF;
78
	}
79
80
	// process output
81
	using enum Mode;
82
	uint8_t 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
101
					retval = DATA_ERROR_TOKEN_OUT_OF_RANGE;
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
			uint8_t 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
			uint8_t 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
	using enum Mode;
215
	transferDelayCounter = 2;
216
	uint8_t command = cmdBuf[0] & 0x3F;
217
	switch (command) {
218
	case 0:  // GO_IDLE_STATE
219
		responseQueue.clear();
220
		mode = COMMAND;
221
		responseQueue.push_back(R1_IDLE);
222
		break;
223
	case 8:  // SEND_IF_COND
224
		// conditions are always OK
225
		responseQueue.push_back({
226
			R1_IDLE,    // R1 (OK) SDHC (checked by MegaSD and FUZIX)
227
			uint8_t(0x02), // command version
228
			uint8_t(0x00), // reserved
229
			uint8_t(0x01), // voltage accepted
230
			cmdBuf[4]});// check pattern
231
		break;
232
	case 9:{ // SEND_CSD
233
		responseQueue.push_back({
234
			R1_BUSY, // OK (ignored on MegaSD code, used in FUZIX)
235
		// now follows a CSD version 2.0 (for SDHC)
236
			START_BLOCK_TOKEN, // data token
237
			uint8_t(0x40),        // CSD_STRUCTURE [127:120]
238
			uint8_t(0x0E),        // (TAAC)
239
			uint8_t(0x00),        // (NSAC)
240
			uint8_t(0x32),        // (TRAN_SPEED)
241
			uint8_t(0x00),        // CCC
242
			uint8_t(0x00),        // CCC / (READ_BL_LEN)
243
			uint8_t(0x00)});      // (RBP)/(WBM)/(RBM)/ DSR_IMP
244
		// SD_CARD_SIZE = (C_SIZE + 1) * 512kByte
245
		auto c_size = narrow<uint32_t>((hd->getNbSectors() * sizeof(sectorBuf)) / size_t(512 * 1024) - 1);
246
		responseQueue.push_back({
247
			uint8_t((c_size >> 16) & 0x3F), // C_SIZE 1
248
			uint8_t((c_size >>  8) & 0xFF), // C_SIZE 2
249
			uint8_t((c_size >>  0) & 0xFF), // C_SIZE 3
250
			uint8_t(0x00),   // res/(EBE)/(SS1)
251
			uint8_t(0x00),   // (SS2)/(WGS)
252
			uint8_t(0x00),   // (WGE)/res/(RF)/(WBL1)
253
			uint8_t(0x00),   // (WBL2)/(WBP)/res
254
			uint8_t(0x00),   // (FFG)/COPY/PWP/TWP/(FF)/res
255
			uint8_t(0x01)}); // CRC / 1
256
		break;}
257
	case 10: // SEND_CID
258
		responseQueue.push_back({
259
			R1_BUSY, // OK (ignored on MegaSD, unused in FUZIX so far)
260
			START_BLOCK_TOKEN, // data token
261
			uint8_t(0xAA),   // CID01 // manuf ID
262
			uint8_t('o' ),   // CID02 // OEM/App ID 1
263
			uint8_t('p' ),   // CID03 // OEM/App ID 2
264
			uint8_t('e' ),   // CID04 // Prod name 1
265
			uint8_t('n' ),   // CID05 // Prod name 2
266
			uint8_t('M' ),   // CID06 // Prod name 3
267
			uint8_t('S' ),   // CID07 // Prod name 4
268
			uint8_t('X' ),   // CID08 // Prod name 5
269
			uint8_t(0x01),   // CID09 // Prod Revision
270
			uint8_t(0x12),   // CID10 // Prod Serial 1
271
			uint8_t(0x34),   // CID11 // Prod Serial 2
272
			uint8_t(0x56),   // CID12 // Prod Serial 3
273
			uint8_t(0x78),   // CID13 // Prod Serial 4
274
			uint8_t(0x00),   // CID14 // reserved / Y1
275
			uint8_t(0xE6),   // CID15 // Y2 / M
276
			uint8_t(0x01)}); // CID16 // CRC / not used
277
		break;
278
	case 12: // STOP TRANSMISSION
279
		responseQueue.push_back(R1_IDLE); // R1 (OK)
280
		mode = COMMAND;
281
		break;
282
	case 16: // SET_BLOCK_LEN
283
		responseQueue.push_back(R1_IDLE); // OK, we don't really care
284
		break;
285
	case 17: // READ_SINGLE_BLOCK
286
	case 18: // READ_MULTIPLE_BLOCK
287
	case 24: // WRITE_BLOCK
288
	case 25: // WRITE_MULTIPLE_BLOCK
289
		// SDHC so the address is the sector
290
		currentSector = Endian::readB32(&cmdBuf[1]);
291
		if (currentSector >= hd->getNbSectors()) {
292
			responseQueue.push_back(R1_PARAMETER_ERROR);
293
		} else {
294
			responseQueue.push_back(R1_BUSY);
295
			switch (command) {
296
				case 17: mode = READ; break;
297
				case 18: mode = MULTI_READ; break;
298
				case 24: mode = WRITE; break;
299
				case 25: mode = MULTI_WRITE; break;
300
				default: UNREACHABLE;
301
			}
302
			currentByteInSector = -1; // wait for token
303
		}
304
		break;
305
	case 41: // implementation of ACMD 41!!
306
		 // SD_SEND_OP_COND
307
		responseQueue.push_back(R1_BUSY);
308
		break;
309
	case 55: // APP_CMD
310
		// TODO: go to ACMD mode, but not necessary now
311
		responseQueue.push_back(R1_IDLE);
312
		break;
313
	case 58: // READ_OCR
314
		responseQueue.push_back({
315
			R1_BUSY,// R1 (OK) (ignored on MegaSD, checked in FUZIX)
316
			uint8_t(0x40),   // OCR Reg part 1 (SDHC: CCS=1)
317
			uint8_t(0x00),   // OCR Reg part 2
318
			uint8_t(0x00),   // OCR Reg part 3
319
			uint8_t(0x00)}); // OCR Reg part 4
320
		break;
321
322
	default:
323
		responseQueue.push_back(R1_ILLEGAL_COMMAND);
324
		break;
325
	}
326
}
327
328
static constexpr auto modeInfo = std::to_array<enum_string<SdCard::Mode>>({
329
	{ "COMMAND",     SdCard::Mode::COMMAND  },
330
	{ "READ",        SdCard::Mode::READ },
331
	{ "MULTI_READ",  SdCard::Mode::MULTI_READ },
332
	{ "WRITE",       SdCard::Mode::WRITE },
333
	{ "MULTI_WRITE", SdCard::Mode::MULTI_WRITE },
334
});
335
SERIALIZE_ENUM(SdCard::Mode, modeInfo);
336
337
template<typename Archive>
338
void SdCard::serialize(Archive& ar, unsigned /*version*/)
339
{
340
	ar.serialize("mode",   mode,
341
	             "cmdBuf", cmdBuf);
342
	ar.serialize_blob("sectorBuf", sectorBuf.raw);
343
	if (hd) ar.serialize("hd", *hd);
344
	ar.serialize("cmdIdx",               cmdIdx,
345
	             "transferDelayCounter", transferDelayCounter,
346
	             "responseQueue",        responseQueue,
347
	             "currentSector",        currentSector,
348
	             "currentByteInSector",  currentByteInSector);
349
}
350
INSTANTIATE_SERIALIZE_METHODS(SdCard);
351
352
} // namespace openmsx