OXYGENHUB
◆ main enhanced-x16-emulator / src / sdcard.c
15.6 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Commander X16 Emulator
2
// Copyright (c) 2019 Michael Steil
3
// Copyright (c) 2020 Frank van den Hoef
4
// All rights reserved. License: 2-clause BSD
5
#ifndef __APPLE__
6
#ifndef _MSC_VER
7
#define _XOPEN_SOURCE   600
8
#define _POSIX_C_SOURCE 1
9
#endif
10
#endif
11
12
#include <limits.h>
13
#include <stdio.h>
14
#include <stdbool.h>
15
#include <string.h>
16
#include "compat.h"
17
#include "sdcard.h"
18
#include "sdcard_fat.h"
19
#include "io_trace.h"
20
#include "files.h"
21
22
//#define VERBOSE 1
23
24
// MMC/SD command (SPI mode)
25
enum {
26
	CMD0   = 0,         // GO_IDLE_STATE
27
	CMD1   = 1,         // SEND_OP_COND
28
	ACMD41 = 0x80 | 41, // SEND_OP_COND (SDC)
29
	CMD8   = 8,         // SEND_IF_COND
30
	CMD9   = 9,         // SEND_CSD
31
	CMD10  = 10,        // SEND_CID
32
	CMD12  = 12,        // STOP_TRANSMISSION
33
	CMD13  = 13,        // SEND_STATUS
34
	ACMD13 = 0x80 | 13, // SD_STATUS (SDC)
35
	CMD16  = 16,        // SET_BLOCKLEN
36
	CMD17  = 17,        // READ_SINGLE_BLOCK
37
	CMD18  = 18,        // READ_MULTIPLE_BLOCK
38
	CMD23  = 23,        // SET_BLOCK_COUNT
39
	ACMD23 = 0x80 | 23, // SET_WR_BLK_ERASE_COUNT (SDC)
40
	CMD24  = 24,        // WRITE_BLOCK
41
	CMD25  = 25,        // WRITE_MULTIPLE_BLOCK
42
	CMD32  = 32,        // ERASE_WR_BLK_START
43
	CMD33  = 33,        // ERASE_WR_BLK_END
44
	CMD38  = 38,        // ERASE
45
	CMD55  = 55,        // APP_CMD
46
	CMD58  = 58,        // READ_OCR
47
};
48
49
static char sdcard_path[PATH_MAX] = "";
50
static struct x16file *sdcard_file = NULL;
51
bool sdcard_attached = false;
52
53
static uint8_t rxbuf[3 + 512];
54
static int rxbuf_idx;
55
static uint32_t lba;
56
static uint8_t last_cmd;
57
static bool is_acmd = false;
58
static bool is_idle = true;
59
static bool is_initialized = false;
60
static bool ongoing_multiblock_read = false;
61
62
static const uint8_t *response = NULL;
63
static int response_length = 0;
64
static int response_counter = 0;
65
66
static bool selected = false;
67
68
// Session counters — reset in sdcard_attach()/sdcard_detach().
69
static uint64_t stat_blocks_read    = 0;
70
static uint64_t stat_blocks_written = 0;
71
static uint64_t stat_bytes_read     = 0;
72
static uint64_t stat_bytes_written  = 0;
73
static uint32_t stat_commands       = 0;
74
75
void
76
sdcard_set_path(char const *path)
77
{
78
	sdcard_detach();
79
80
	strncpy(sdcard_path, path, PATH_MAX);
81
	sdcard_path[PATH_MAX-1] = '\0';
82
83
	sdcard_attach();
84
}
85
86
bool
87
sdcard_path_is_set()
88
{
89
	return strlen(sdcard_path) > 0;
90
}
91
92
const char *
93
sdcard_get_path(void)
94
{
95
	return sdcard_path;
96
}
97
98
void
99
sdcard_attach()
100
{
101
	if (!sdcard_attached && sdcard_path_is_set()) {
102
		sdcard_file = x16open(sdcard_path, "r+b");
103
		if(sdcard_file == NULL) {
104
			printf("Cannot open SDCard file %s!\n", sdcard_path);
105
			return;
106
		}
107
108
		printf("SD card attached.\n");
109
		sdcard_attached = true;
110
		is_initialized  = false;
111
112
		// Reset session counters.
113
		stat_blocks_read    = 0;
114
		stat_blocks_written = 0;
115
		stat_bytes_read     = 0;
116
		stat_bytes_written  = 0;
117
		stat_commands       = 0;
118
119
		// Build the FAT index (best-effort; failure is non-fatal).
120
		if (sdcard_fat_autoindex) {
121
			sdcard_fat_build(sdcard_file);
122
		}
123
	}
124
}
125
126
// Rebuild the filesystem index over the attached image. The debugger offers
127
// this because the index goes stale as soon as the machine writes to a FAT or
128
// a directory, and it lives here rather than in the UI because the card owns
129
// the file handle the index has to borrow.
130
bool
131
sdcard_reindex()
132
{
133
	if (!sdcard_attached || sdcard_file == NULL) {
134
		return false;
135
	}
136
	sdcard_fat_free();
137
	return sdcard_fat_build(sdcard_file);
138
}
139
140
void
141
sdcard_detach()
142
{
143
	if (sdcard_attached) {
144
		sdcard_fat_free();
145
		x16close(sdcard_file);
146
		sdcard_file = NULL;
147
148
		printf("SD card detached.\n");
149
		sdcard_attached = false;
150
151
		// Reset session counters.
152
		stat_blocks_read    = 0;
153
		stat_blocks_written = 0;
154
		stat_bytes_read     = 0;
155
		stat_bytes_written  = 0;
156
		stat_commands       = 0;
157
	}
158
}
159
160
void
161
sdcard_select(bool select)
162
{
163
	selected = select;
164
	rxbuf_idx = 0;
165
#if defined(VERBOSE) && VERBOSE >= 2
166
	printf("*** SD card select: %u\n", select);
167
#endif
168
}
169
170
static void
171
set_response_csd(void)
172
{
173
	static uint8_t rr[] = {
174
		0xff, // dummy
175
		0xff, // dummy
176
		0x00, // R1 response
177
		0xff, // dummy
178
		0xfe, // begin block
179
		0x40, // CSD_STRUCTURE [7:6] = 1, RESERVED [5:0] = 0
180
		0x0e, // TAAC = 0x0e
181
		0x00, // NSAC = 0x00
182
		0x32, // TRAN_SPEED = 0x32
183
		0x5b, // CCC (11:4)
184
		0x59, // CCC (3:0) [7-4], READ_BL_LEN [3-0]
185
		0x00, // READ_BL_PARTIAL [7], WRITE_BLK_MISALIGN [6], READ_BLK_MISALIGN [5], DSR_IMP [4], RESERVED [3:0]
186
		0x00, // RESERVED [7:6], C_SIZE(21:16) [5:0]
187
		0x00, // C_SIZE(15:8)
188
		0x00, // C_SIZE(7:0)
189
		0x7f, // RESERVED [7], ERASE_BLK_EN[6], SECTOR_SIZE(6:1) [5:0]
190
		0x80, // SECTOR_SIZE(0) [7], WP_GRP_SIZE [6:0]
191
		0x0a, // WP_GRP_ENABLE [7], RESERVED [6:5], R2W_FACTOR [4:2], WRITE_BL_LEN (3:2) [1:0]
192
		0x40, // WRITE_BL_LEN (1:0) [7:6], WRITE_BL_PARTIAL [5], RESERVED [4:0]
193
		0x00, // FILE_FORMAT_GRP [7] = 0, COPY [6], PERM_WRITE_PROTECT [5], TMP_WRITE_PROTECT [4], RESERVED [3:0]
194
		0x01 // CRC[7:1], ALWAYS_1 [0]
195
		};
196
	uint64_t c_size = (x16size(sdcard_file) >> 19)-1;
197
	rr[12] |= (c_size >> 16) & 0x3f;
198
	rr[13] = (c_size >> 8) & 0xff;
199
	rr[14] = c_size & 0xff;
200
201
	response = rr;
202
	response_length = 21;
203
}
204
205
static void
206
set_response_r1(void)
207
{
208
	static uint8_t r1;
209
	r1 = is_idle ? 1 : 0;
210
	response = &r1;
211
	response_length = 1;
212
}
213
214
static void
215
set_response_r2(void)
216
{
217
	if (is_initialized) {
218
		static const uint8_t r2[] = {0x00, 0x00};
219
		response = r2;
220
		response_length = sizeof(r2);
221
	} else {
222
		static const uint8_t r2[] = {0x1F, 0xFF};
223
		response = r2;
224
		response_length = sizeof(r2);
225
	}
226
}
227
228
static void
229
set_response_r3(void)
230
{
231
	static const uint8_t r3[] = {0xC0, 0xFF, 0x80, 0x00};
232
	response = r3;
233
	response_length = sizeof(r3);
234
}
235
236
static void
237
set_response_r7(void)
238
{
239
	static const uint8_t r7[] = {1, 0x00, 0x00, 0x01, 0xAA};
240
	response = r7;
241
	response_length = sizeof(r7);
242
}
243
244
// ── Trace helpers ─────────────────────────────────────────────────────────────
245
// All trace work is gated on io_trace_wants(); the helpers are no-ops when the
246
// ring is not capturing SD events so the hot path costs one branch.
247
248
// Emit a "read lba=N <region or path>" event.  Called after data actually
249
// moves; safe to call from loadBlock() and the write handler.
250
static void
251
trace_block(uint32_t lba_val, bool is_write)
252
{
253
	if (!io_trace_wants(IO_DEV_SDCARD)) {
254
		return;
255
	}
256
	const char              *verb = is_write ? "write" : "read";
257
	const sdcard_fat_file_t *f    = NULL;
258
	uint64_t                 off  = 0;
259
	sdcard_fat_region_t      reg  = sdcard_fat_lookup(lba_val, &f, &off);
260
261
	if ((reg == SDCARD_FAT_REGION_FILE || reg == SDCARD_FAT_REGION_DIR) && f != NULL) {
262
		io_trace_event(IO_DEV_SDCARD, "%s lba=%u %s+%u",
263
		               verb, lba_val, f->path, (uint32_t)off);
264
		return;
265
	}
266
	const char *tag = NULL;
267
	switch (reg) {
268
		case SDCARD_FAT_REGION_FAT:      tag = "<FAT>";           break;
269
		case SDCARD_FAT_REGION_ROOTDIR:  tag = "<root dir>";      break;
270
		case SDCARD_FAT_REGION_MBR:      tag = "<MBR>";           break;
271
		case SDCARD_FAT_REGION_RESERVED: tag = "<boot/reserved>"; break;
272
		case SDCARD_FAT_REGION_FREE:     tag = "<unallocated>";   break;
273
		default:                         tag = NULL;              break;
274
	}
275
	if (tag != NULL) {
276
		io_trace_event(IO_DEV_SDCARD, "%s lba=%u %s", verb, lba_val, tag);
277
	} else {
278
		io_trace_event(IO_DEV_SDCARD, "%s lba=%u", verb, lba_val);
279
	}
280
}
281
282
// Emit a decoded command event.  Called after the switch so that lba is
283
// already updated for CMD17/18/24.  The argument bytes are still in rxbuf[1..4].
284
static void
285
trace_command(void)
286
{
287
	if (!io_trace_wants(IO_DEV_SDCARD)) {
288
		return;
289
	}
290
	uint8_t  cmd  = last_cmd;
291
	uint32_t arg  = ((uint32_t)rxbuf[1] << 24) | ((uint32_t)rxbuf[2] << 16)
292
	              | ((uint32_t)rxbuf[3] <<  8)  |  (uint32_t)rxbuf[4];
293
	uint8_t  num  = cmd & 0x3Fu;
294
295
	if (cmd & 0x80u) {
296
		// ACMD
297
		switch (cmd) {
298
			case ACMD41:
299
				io_trace_event(IO_DEV_SDCARD, "ACMD41 SD_SEND_OP_COND");
300
				break;
301
			default:
302
				io_trace_event(IO_DEV_SDCARD, "ACMD%u arg=0x%08X", num, arg);
303
				break;
304
		}
305
		return;
306
	}
307
	switch (cmd) {
308
		case CMD0:  io_trace_event(IO_DEV_SDCARD, "CMD0 GO_IDLE_STATE");               break;
309
		case CMD1:  io_trace_event(IO_DEV_SDCARD, "CMD1 SEND_OP_COND");                break;
310
		case CMD8:  io_trace_event(IO_DEV_SDCARD, "CMD8 SEND_IF_COND");                break;
311
		case CMD9:  io_trace_event(IO_DEV_SDCARD, "CMD9 SEND_CSD");                    break;
312
		case CMD10: io_trace_event(IO_DEV_SDCARD, "CMD10 SEND_CID");                   break;
313
		case CMD12: io_trace_event(IO_DEV_SDCARD, "CMD12 STOP_TRANSMISSION");          break;
314
		case CMD13: io_trace_event(IO_DEV_SDCARD, "CMD13 SEND_STATUS");                break;
315
		case CMD16: io_trace_event(IO_DEV_SDCARD, "CMD16 SET_BLOCKLEN len=%u", arg);   break;
316
		case CMD17: io_trace_event(IO_DEV_SDCARD, "CMD17 READ_SINGLE_BLOCK lba=%u",    arg); break;
317
		case CMD18: io_trace_event(IO_DEV_SDCARD, "CMD18 READ_MULTIPLE_BLOCK lba=%u",  arg); break;
318
		case CMD24: io_trace_event(IO_DEV_SDCARD, "CMD24 WRITE_BLOCK lba=%u",          arg); break;
319
		case CMD25: io_trace_event(IO_DEV_SDCARD, "CMD25 WRITE_MULTIPLE_BLOCK lba=%u", arg); break;
320
		case CMD55: io_trace_event(IO_DEV_SDCARD, "CMD55 APP_CMD");                    break;
321
		case CMD58: io_trace_event(IO_DEV_SDCARD, "CMD58 READ_OCR");                   break;
322
		default:
323
			io_trace_event(IO_DEV_SDCARD, "CMD%u arg=0x%08X", num, arg);
324
			break;
325
	}
326
}
327
328
// Return length of reply
329
static int loadBlock(uint8_t *dest)
330
{
331
	int response_length = 0;
332
333
	dest[0] = 0xFE; // Data token for CMD17/18
334
#ifdef VERBOSE
335
	printf("*** SD Reading LBA %d\n", lba);
336
#endif
337
	if ((Sint64)lba * 512 >= x16size(sdcard_file)) {
338
		dest[0] = 0x08; // Error token: out of range
339
		if (io_trace_wants(IO_DEV_SDCARD)) {
340
			io_trace_event(IO_DEV_SDCARD, "ERROR read lba=%u out of range", lba);
341
		}
342
		response_length = 1;
343
	} else {
344
		x16seek(sdcard_file, (Sint64)lba * 512, XSEEK_SET);
345
		int bytes_read = x16read(sdcard_file, &dest[1], 1, 512);
346
		if (bytes_read != 512) {
347
			printf("Warning: short read!\n");
348
		}
349
		stat_blocks_read++;
350
		stat_bytes_read += 512;
351
		sdcard_fat_note_access(lba, false, 512);
352
		trace_block(lba, false);
353
		response_length = 1 + 512 + 2;
354
	}
355
	return response_length;
356
}
357
358
uint8_t
359
sdcard_handle(uint8_t inbyte)
360
{
361
	if (!selected || (sdcard_file == NULL)) {
362
		return 0xFF;
363
	}
364
	// printf("sdcard_handle: %02X\n", inbyte);
365
366
	uint8_t outbyte = 0xFF;
367
368
	if (rxbuf_idx == 0 && inbyte == 0xFF) {
369
		// send response data
370
		if (response) {
371
			outbyte = response[response_counter++];
372
			if (response_counter == response_length) {
373
				if (ongoing_multiblock_read) {
374
					static uint8_t read_multiblock_next_response[1 + 512 + 2];
375
					// Prepare next multiblock reply
376
					lba++;
377
					response_length = loadBlock(&read_multiblock_next_response[0]);
378
					// Stop multiblock read if error
379
					if (response_length == 1) {
380
						ongoing_multiblock_read = false;
381
					}
382
					response = read_multiblock_next_response;
383
					response_counter = 0;
384
				} else  {
385
					response = NULL;
386
					ongoing_multiblock_read = false;
387
				}
388
			}
389
		}
390
391
	} else {
392
		rxbuf[rxbuf_idx++] = inbyte;
393
394
		if ((rxbuf[0] & 0xC0) == 0x40 && rxbuf_idx == 6) {
395
			rxbuf_idx = 0;
396
397
			// Check for start-bit + transmission bit
398
			if ((rxbuf[0] & 0xC0) != 0x40) {
399
				response = NULL;
400
				return 0xFF;
401
			}
402
			rxbuf[0] &= 0x3F;
403
404
			// Use upper command bit to indicate this is an ACMD
405
			if (is_acmd) {
406
				rxbuf[0] |= 0x80;
407
				is_acmd = false;
408
			}
409
410
			last_cmd = rxbuf[0];
411
			stat_commands++;
412
413
#if defined(VERBOSE) && VERBOSE >= 2
414
			printf("*** SD %sCMD%d -> Response:", (rxbuf[0] & 0x80) ? "A" : "", rxbuf[0] & 0x3F);
415
#endif
416
			switch (rxbuf[0]) {
417
				case CMD0: {
418
					// GO_IDLE_STATE: Resets the SD Memory Card
419
					is_idle = true;
420
					set_response_r1();
421
					break;
422
				}
423
424
				case CMD8: {
425
					// SEND_IF_COND: Sends SD Memory Card interface condition that includes host supply voltage
426
					set_response_r7();
427
					break;
428
				}
429
430
				case CMD9: {
431
					// SEND_CSD: Sends card-specific data
432
					set_response_csd();
433
					break;
434
				}
435
436
				case ACMD41: {
437
					// SD_SEND_OP_COND: Sends host capacity support information and activated the card's initialization process
438
					is_idle = false;
439
					is_initialized = true;
440
					set_response_r1();
441
					break;
442
				}
443
444
				case CMD12: {
445
					// STOP_TRANSMISSION: Abort ongoing multiple block read
446
					ongoing_multiblock_read = false;
447
					set_response_r1();
448
					break;
449
				}
450
451
				case CMD13: {
452
					// SEND_STATUS: Asks the selected card to send its status register
453
					set_response_r2();
454
					break;
455
				}
456
				case CMD16: {
457
					// SET_BLOCKLEN: In case of non-SDHC card, this sets the block length. Block length of SDHC/SDXC cards are fixed to 512 bytes.
458
					set_response_r1();
459
					break;
460
				}
461
				case CMD18: {
462
					// READ_MULTIPLE_BLOCK
463
					ongoing_multiblock_read = true;
464
					// Fallthrough
465
				}
466
				case CMD17: {
467
					// READ_SINGLE_BLOCK
468
					lba = (rxbuf[1] << 24) | (rxbuf[2] << 16) | (rxbuf[3] << 8) | rxbuf[4];
469
					static uint8_t read_block_response[2 + 512 + 2];
470
					read_block_response[0] = 0; // R1 response to command
471
					response_length = 1 + loadBlock(&read_block_response[1]);
472
					// Stop multiblock read if error
473
					if (response_length == 2) {
474
						ongoing_multiblock_read = false;
475
					}
476
					response = read_block_response;
477
					break;
478
				}
479
480
				case CMD24: {
481
					// WRITE_BLOCK
482
					lba = (rxbuf[1] << 24) | (rxbuf[2] << 16) | (rxbuf[3] << 8) | rxbuf[4];
483
					if (rxbuf_idx > 4 && (Sint64)lba * 512 >= x16size(sdcard_file)) {
484
						static uint8_t bad_lba[2] = {0x00, 0x08};
485
						response = bad_lba;
486
						response_length = 2;
487
					} else {
488
						set_response_r1();
489
					}
490
					break;
491
				}
492
493
				case CMD55: {
494
					// APP_CMD: Next command is an application specific command
495
					is_acmd = true;
496
					set_response_r1();
497
					break;
498
				}
499
500
				case CMD58: {
501
					// READ_OCR: Read the OCR register of the card
502
					set_response_r3();
503
					break;
504
				}
505
506
				default: {
507
					set_response_r1();
508
					break;
509
				}
510
			}
511
			response_counter = 0;
512
			trace_command();
513
514
#if defined(VERBOSE) && VERBOSE >= 2
515
			for (int i = 0; i < (response_length < 16 ? response_length : 16); i++) {
516
				printf(" %02X", response[i]);
517
			}
518
			printf("\n");
519
#endif
520
521
		} else if (rxbuf_idx == 515) {
522
			rxbuf_idx = 0;
523
			// Check for 'start block' byte
524
			if (last_cmd == CMD24 && rxbuf[0] == 0xFE) {
525
#ifdef VERBOSE
526
				printf("*** SD Writing LBA %d\n", lba);
527
#endif
528
				if ((Sint64)lba * 512 >= x16size(sdcard_file)) {
529
					// do nothing?
530
						if (io_trace_wants(IO_DEV_SDCARD)) {
531
							io_trace_event(IO_DEV_SDCARD, "ERROR write lba=%u out of range", lba);
532
						}
533
					} else {
534
						x16seek(sdcard_file, (Sint64)lba * 512, XSEEK_SET);
535
						int bytes_written = x16write(sdcard_file, rxbuf + 1, 1, 512);
536
						if (bytes_written != 512) {
537
							printf("Warning: short write!\n");
538
						}
539
						stat_blocks_written++;
540
						stat_bytes_written += 512;
541
						sdcard_fat_note_access(lba, true, 512);
542
						trace_block(lba, true);
543
					}
544
			}
545
		}
546
	}
547
	return outbyte;
548
}
549
550
void
551
sdcard_debug_get_state(sdcard_debug_state_t *out)
552
{
553
	if (out == NULL) {
554
		return;
555
	}
556
	out->attached              = sdcard_attached;
557
	out->selected              = selected;
558
	out->image_path            = sdcard_path;
559
	out->image_size            = sdcard_file ? x16size(sdcard_file) : -1;
560
	out->last_cmd              = last_cmd & 0x3F;
561
	out->last_cmd_is_acmd      = (last_cmd & 0x80) != 0;
562
	out->last_lba              = lba;
563
	out->is_idle               = is_idle;
564
	out->is_initialized        = is_initialized;
565
	out->ongoing_multiblock_read = ongoing_multiblock_read;
566
	out->response_length       = response_length;
567
	out->response_counter      = response_counter;
568
	out->blocks_read           = stat_blocks_read;
569
	out->blocks_written        = stat_blocks_written;
570
	out->bytes_read            = stat_bytes_read;
571
	out->bytes_written         = stat_bytes_written;
572
	out->commands              = stat_commands;
573
}