src/wd2010.c

changeset 112
a392eb8f9806
child 115
da3d10af0711
     1.1 diff -r 4c85846b09cd -r a392eb8f9806 src/wd2010.c
     1.2 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.3 +++ b/src/wd2010.c	Sat Nov 17 19:18:29 2012 +0000
     1.4 @@ -0,0 +1,430 @@
     1.5 +#include <stdint.h>
     1.6 +#include <stdbool.h>
     1.7 +#include <malloc.h>
     1.8 +#include "SDL.h"
     1.9 +#include "musashi/m68k.h"
    1.10 +#include "wd2010.h"
    1.11 +
    1.12 +#define WD2010_DEBUG
    1.13 +
    1.14 +#ifndef WD2010_DEBUG
    1.15 +#define NDEBUG
    1.16 +#endif
    1.17 +#include "utils.h"
    1.18 +
    1.19 +#define SEEK_DELAY 30
    1.20 +
    1.21 +#define CMD_ENABLE_RETRY 0x01
    1.22 +#define CMD_LONG_MODE 0x02
    1.23 +#define CMD_MULTI_SECTOR 0x04
    1.24 +#define CMD_INTRQ_WHEN_COMPLETE 0x08
    1.25 +
    1.26 +#define ER_BAD_BLOCK 0x80
    1.27 +#define ER_CRC 0x40
    1.28 +#define ER_ID_NOT_FOUND 0x10
    1.29 +#define ER_ABORTED_COMMAND 0x04
    1.30 +#define ER_NO_TK0 0x02
    1.31 +#define ER_NO_ADDRESS_MARK 0x01
    1.32 +
    1.33 +#define SR_BUSY 0x80
    1.34 +#define SR_READY 0x40
    1.35 +#define SR_WRITE_FAULT 0x20
    1.36 +#define SR_SEEK_COMPLETE 0x10
    1.37 +#define SR_DRQ 0x08
    1.38 +#define SR_CORRECTED 0x04
    1.39 +#define SR_COMMAND_IN_PROGRESS 0x02
    1.40 +#define SR_ERROR 0x01
    1.41 +
    1.42 +extern int cpu_log_enabled;
    1.43 +
    1.44 +/// WD2010 command constants
    1.45 +enum {
    1.46 +	CMD_MASK				= 0xF0,		///< Bit mask to detect command bits
    1.47 +	CMD_2010_EXT			= 0x00,		///< WD2010 extended commands (compute correction, set parameter)
    1.48 +	CMD_RESTORE				= 0x10,		///< Restore (recalibrate, seek to track 0)
    1.49 +	CMD_READ_SECTOR			= 0x20,		///< Read sector
    1.50 +	CMD_WRITE_SECTOR		= 0x30,		///< Write sector
    1.51 +	CMD_SCAN_ID				= 0x40,		///< Scan ID
    1.52 +	CMD_WRITE_FORMAT		= 0x50,		///< Write format
    1.53 +	CMD_SEEK				= 0x70,		///< Seek to given track
    1.54 +};
    1.55 +
    1.56 +int wd2010_init(WD2010_CTX *ctx, FILE *fp, int secsz, int spt, int heads)
    1.57 +{
    1.58 +	long filesize;
    1.59 +	wd2010_reset(ctx);
    1.60 +	// Start by finding out how big the image file is
    1.61 +	fseek(fp, 0, SEEK_END);
    1.62 +	filesize = ftell(fp);
    1.63 +	fseek(fp, 0, SEEK_SET);
    1.64 +
    1.65 +	// Now figure out how many tracks it contains
    1.66 +	int tracks = filesize / secsz / spt / heads;
    1.67 +	// Confirm...
    1.68 +	if (tracks < 1) {
    1.69 +		return WD2010_ERR_BAD_GEOM;
    1.70 +	}
    1.71 +
    1.72 +	// Allocate enough memory to store one disc track
    1.73 +	if (ctx->data) {
    1.74 +		free(ctx->data);
    1.75 +	}
    1.76 +	ctx->data = malloc(secsz * spt);
    1.77 +	if (!ctx->data)
    1.78 +		return WD2010_ERR_NO_MEMORY;
    1.79 +
    1.80 +	// Load the image and the geometry data
    1.81 +	ctx->disc_image = fp;
    1.82 +	ctx->geom_tracks = tracks;
    1.83 +	ctx->geom_secsz = secsz;
    1.84 +	ctx->geom_heads = heads;
    1.85 +	ctx->geom_spt = spt;
    1.86 +	return WD2010_ERR_OK;
    1.87 +
    1.88 +}
    1.89 +
    1.90 +void wd2010_reset(WD2010_CTX *ctx)
    1.91 +{
    1.92 +	// track, head and sector unknown
    1.93 +	ctx->track = ctx->head = ctx->sector = 0;
    1.94 +
    1.95 +	// no IRQ pending
    1.96 +	ctx->irq = false;
    1.97 +
    1.98 +	// no data available
    1.99 +	ctx->data_pos = ctx->data_len = 0;
   1.100 +
   1.101 +	// Status register clear, not busy
   1.102 +	ctx->status = 0;
   1.103 +
   1.104 +	ctx->sector_count = 0;
   1.105 +	ctx->sector_number = 0;
   1.106 +	ctx->cylinder_low_reg = 0;
   1.107 +	ctx->cylinder_high_reg = 0;
   1.108 +	ctx->sdh = 0;
   1.109 +}
   1.110 +
   1.111 +void wd2010_done(WD2010_CTX *ctx)
   1.112 +{
   1.113 +	// Reset the WD2010
   1.114 +	wd2010_reset(ctx);
   1.115 +
   1.116 +	// Free any allocated memory
   1.117 +	if (ctx->data) {
   1.118 +		free(ctx->data);
   1.119 +		ctx->data = NULL;
   1.120 +	}
   1.121 +}
   1.122 +
   1.123 +
   1.124 +bool wd2010_get_irq(WD2010_CTX *ctx)
   1.125 +{
   1.126 +	return ctx->irq;
   1.127 +}
   1.128 +
   1.129 +bool wd2010_get_drq(WD2010_CTX *ctx)
   1.130 +{
   1.131 +	return (ctx->drq && ctx->data_pos < ctx->data_len);
   1.132 +}
   1.133 +
   1.134 +void wd2010_dma_miss(WD2010_CTX *ctx)
   1.135 +{
   1.136 +	ctx->data_pos = ctx->data_len;
   1.137 +	ctx->write_pos = 0;
   1.138 +	ctx->status = SR_READY | SR_SEEK_COMPLETE;
   1.139 +	ctx->irq = true;
   1.140 +}
   1.141 +
   1.142 +uint8_t wd2010_read_data(WD2010_CTX *ctx)
   1.143 +{
   1.144 +	// If there's data in the buffer, return it. Otherwise return 0xFF.
   1.145 +	if (ctx->data_pos < ctx->data_len) {
   1.146 +		if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
   1.147 +			ctx->sector_count--;
   1.148 +			ctx->sector_number++;
   1.149 +		}
   1.150 +		// set IRQ if this is the last data byte
   1.151 +		if (ctx->data_pos == (ctx->data_len-1)) {
   1.152 +			ctx->status = SR_READY | SR_SEEK_COMPLETE;
   1.153 +			// Set IRQ
   1.154 +			ctx->irq = true;
   1.155 +			ctx->drq = false;
   1.156 +		}
   1.157 +		// return data byte and increment pointer
   1.158 +		return ctx->data[ctx->data_pos++];
   1.159 +	} else {
   1.160 +		// empty buffer (this shouldn't happen)
   1.161 +		LOG("WD2010: attempt to read from empty data buffer");
   1.162 +		return 0xff;
   1.163 +	}
   1.164 +}
   1.165 +
   1.166 +void wd2010_write_data(WD2010_CTX *ctx, uint8_t val)
   1.167 +{
   1.168 +	// If we're processing a write command, and there's space in the
   1.169 +	// buffer, allow the write.
   1.170 +	if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) {
   1.171 +		// store data byte and increment pointer
   1.172 +		if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
   1.173 +			ctx->sector_count--;
   1.174 +			ctx->sector_number++;
   1.175 +		}
   1.176 +		ctx->data[ctx->data_pos++] = val;
   1.177 +		// set IRQ and write data if this is the last data byte
   1.178 +		if (ctx->data_pos == ctx->data_len) {
   1.179 +			if (!ctx->formatting){
   1.180 +				fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
   1.181 +				fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
   1.182 +				fflush(ctx->disc_image);
   1.183 +			}
   1.184 +			ctx->formatting = false;
   1.185 +			ctx->status = SR_READY | SR_SEEK_COMPLETE;
   1.186 +			// Set IRQ and reset write pointer
   1.187 +			ctx->irq = true;
   1.188 +			ctx->write_pos = -1;
   1.189 +			ctx->drq = false;
   1.190 +		}
   1.191 +	}else{
   1.192 +		LOG("WD2010: attempt to write to data buffer without a write command in progress");
   1.193 +	}
   1.194 +}
   1.195 +
   1.196 +uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx)
   1.197 +{
   1.198 +	/*m68k_end_timeslice();*/
   1.199 +	ctx->status = SR_READY | SR_SEEK_COMPLETE;
   1.200 +	ctx->irq = true;
   1.201 +	return (0);
   1.202 +}
   1.203 +
   1.204 +uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx)
   1.205 +{
   1.206 +	/*m68k_end_timeslice();*/
   1.207 +	ctx->drq = true;
   1.208 +	return (0);
   1.209 +}
   1.210 +
   1.211 +uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr)
   1.212 +{
   1.213 +	uint8_t temp = 0;
   1.214 +
   1.215 +	/*cpu_log_enabled = 1;*/
   1.216 +
   1.217 +	switch (addr & 0x07) {
   1.218 +		case WD2010_REG_ERROR:
   1.219 +			return ctx->error_reg;
   1.220 +		case WD2010_REG_SECTOR_COUNT:
   1.221 +			return ctx->sector_count;
   1.222 +		case WD2010_REG_SECTOR_NUMBER:
   1.223 +			return ctx->sector_number;
   1.224 +		case WD2010_REG_CYLINDER_HIGH:      // High byte of cylinder
   1.225 +			return ctx->cylinder_high_reg;
   1.226 +		case WD2010_REG_CYLINDER_LOW:       // Low byte of cylinder
   1.227 +			return ctx->cylinder_low_reg;
   1.228 +		case WD2010_REG_SDH:
   1.229 +			return ctx->sdh;
   1.230 +		case WD2010_REG_STATUS:             // Status register
   1.231 +			// Read from status register clears IRQ
   1.232 +			ctx->irq = false;
   1.233 +			// Get current status flags (set by last command)
   1.234 +			// DRQ bit
   1.235 +			if (ctx->cmd_has_drq) {
   1.236 +				temp = ctx->status & ~(SR_BUSY & SR_DRQ);
   1.237 +				temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0;
   1.238 +				LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
   1.239 +			} else {
   1.240 +				temp = ctx->status & ~0x80;
   1.241 +			}
   1.242 +			/*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/
   1.243 +			// HDC is busy if there is still data in the buffer
   1.244 +			temp |= (ctx->data_pos < ctx->data_len) ? SR_BUSY : 0;	// if data in buffer, then DMA hasn't copied it yet, and we're still busy!
   1.245 +																	// TODO: also if seek delay / read delay hasn't passed (but that's for later)
   1.246 +			/*XXX: should anything else be set here?*/
   1.247 +			return temp;
   1.248 +		default:
   1.249 +			// shut up annoying compilers which don't recognise unreachable code when they see it
   1.250 +			// (here's looking at you, gcc!)
   1.251 +			return 0xff;
   1.252 +	}
   1.253 +}
   1.254 +
   1.255 +
   1.256 +void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val)
   1.257 +{
   1.258 +	uint8_t cmd = val & CMD_MASK;
   1.259 +	size_t lba;
   1.260 +	int new_track;
   1.261 +	int sector_count;
   1.262 +
   1.263 +	m68k_end_timeslice();
   1.264 +
   1.265 +	/*cpu_log_enabled = 1;*/
   1.266 +
   1.267 +	switch (addr & 0x07) {
   1.268 +		case WD2010_REG_WRITE_PRECOMP_CYLINDER:
   1.269 +			break;
   1.270 +		case WD2010_REG_SECTOR_COUNT:
   1.271 +			ctx->sector_count = val;
   1.272 +			break;
   1.273 +		case WD2010_REG_SECTOR_NUMBER:
   1.274 +			ctx->sector_number = val;
   1.275 +			break;
   1.276 +		case WD2010_REG_CYLINDER_HIGH:		// High byte of cylinder
   1.277 +			ctx->cylinder_high_reg = val;
   1.278 +			break;
   1.279 +		case WD2010_REG_CYLINDER_LOW:		// Low byte of cylinder
   1.280 +			ctx->cylinder_low_reg = val;
   1.281 +			break;
   1.282 +		case WD2010_REG_SDH:
   1.283 +			/*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/
   1.284 +			ctx->data_pos = ctx->data_len = 0;
   1.285 +			ctx->sdh = val;
   1.286 +			break;
   1.287 +		case WD2010_REG_COMMAND:	// Command register
   1.288 +			// write to command register clears interrupt request
   1.289 +			ctx->irq = false;
   1.290 +			ctx->error_reg = 0;
   1.291 +
   1.292 +			/*cpu_log_enabled = 1;*/
   1.293 +			switch (cmd) {
   1.294 +				case CMD_RESTORE:
   1.295 +					// Restore. Set track to 0 and throw an IRQ.
   1.296 +					ctx->track = 0;
   1.297 +					SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
   1.298 +					break;
   1.299 +				case CMD_SCAN_ID:
   1.300 +					ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff;
   1.301 +					ctx->cylinder_low_reg = ctx->track & 0xff;
   1.302 +					ctx->sector_number = ctx->sector;
   1.303 +					ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7);
   1.304 +				case CMD_WRITE_FORMAT:
   1.305 +				case CMD_SEEK:
   1.306 +				case CMD_READ_SECTOR:
   1.307 +				case CMD_WRITE_SECTOR:
   1.308 +					// Seek. Seek to the track specced in the cylinder
   1.309 +					// registers.
   1.310 +					new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg;
   1.311 +					if (new_track < ctx->geom_tracks) {
   1.312 +						ctx->track = new_track;
   1.313 +					} else {
   1.314 +						// Seek error. :(
   1.315 +						ctx->status = SR_ERROR;
   1.316 +						ctx->error_reg = ER_ID_NOT_FOUND;
   1.317 +						ctx->irq = true;
   1.318 +						break;
   1.319 +					}
   1.320 +					ctx->head = ctx->sdh & 0x07;
   1.321 +					ctx->sector = ctx->sector_number;
   1.322 +
   1.323 +					ctx->formatting = cmd == CMD_WRITE_FORMAT;
   1.324 +					switch (cmd){
   1.325 +						case CMD_SEEK:
   1.326 +							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
   1.327 +							break;
   1.328 +						case CMD_READ_SECTOR:
   1.329 +							/*XXX: does a separate function to set the head have to be added?*/
   1.330 +							LOG("WD2010: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   1.331 +
   1.332 +							// Read Sector
   1.333 +
   1.334 +							// Check to see if the cyl, hd and sec are valid
   1.335 +							if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt - 1)) {
   1.336 +								LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   1.337 +										ctx->track, ctx->head, ctx->sector,
   1.338 +										ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt-1);
   1.339 +								// CHS parameters exceed limits
   1.340 +								ctx->status = SR_ERROR;
   1.341 +								ctx->error_reg = ER_ID_NOT_FOUND;
   1.342 +								// Set IRQ
   1.343 +								ctx->irq = true;
   1.344 +								break;
   1.345 +							}
   1.346 +
   1.347 +							// reset data pointers
   1.348 +							ctx->data_pos = ctx->data_len = 0;
   1.349 +
   1.350 +							if (val & CMD_MULTI_SECTOR){
   1.351 +								ctx->multi_sector = 1;
   1.352 +								sector_count = ctx->sector_count;
   1.353 +							}else{
   1.354 +								ctx->multi_sector = 0;
   1.355 +								sector_count = 1;
   1.356 +							}
   1.357 +							for (int i=0; i<sector_count; i++) {
   1.358 +								// Calculate the LBA address of the required sector
   1.359 +								// LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
   1.360 +								lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i);
   1.361 +								// convert LBA to byte address
   1.362 +								lba *= ctx->geom_secsz;
   1.363 +								LOG("\tREAD lba = %lu", lba);
   1.364 +
   1.365 +								// Read the sector from the file
   1.366 +								fseek(ctx->disc_image, lba, SEEK_SET);
   1.367 +								// TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   1.368 +								ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
   1.369 +								LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
   1.370 +							}
   1.371 +
   1.372 +							ctx->status = 0;
   1.373 +							ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
   1.374 +							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
   1.375 +
   1.376 +							break;
   1.377 +						case CMD_WRITE_FORMAT:
   1.378 +							ctx->sector = 0;
   1.379 +						case CMD_WRITE_SECTOR:
   1.380 +							LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   1.381 +							// Read Sector
   1.382 +
   1.383 +							// Check to see if the cyl, hd and sec are valid
   1.384 +							if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt-1)) {
   1.385 +								LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   1.386 +										ctx->track, ctx->head, ctx->sector,
   1.387 +										ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
   1.388 +								// CHS parameters exceed limits
   1.389 +								ctx->status = SR_ERROR;
   1.390 +								ctx->error_reg = ER_ID_NOT_FOUND;
   1.391 +								// Set IRQ
   1.392 +								ctx->irq = true;
   1.393 +								break;
   1.394 +							}
   1.395 +
   1.396 +							// reset data pointers
   1.397 +							ctx->data_pos = ctx->data_len = 0;
   1.398 +
   1.399 +							if (val & CMD_MULTI_SECTOR){
   1.400 +								ctx->multi_sector = 1;
   1.401 +								sector_count = ctx->sector_count;
   1.402 +							}else{
   1.403 +								ctx->multi_sector = 0;
   1.404 +								sector_count = 1;
   1.405 +							}
   1.406 +							ctx->data_len = ctx->geom_secsz * sector_count;
   1.407 +							lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector));
   1.408 +							// convert LBA to byte address
   1.409 +							ctx->write_pos = lba * ctx->geom_secsz;
   1.410 +							LOG("\tWRITE lba = %lu", lba);
   1.411 +
   1.412 +							ctx->status = 0;
   1.413 +							ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
   1.414 +							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
   1.415 +
   1.416 +							break;
   1.417 +						default:
   1.418 +							LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd);
   1.419 +							break;
   1.420 +					}
   1.421 +					break;
   1.422 +				case CMD_2010_EXT: /* not implemented */
   1.423 +				default:
   1.424 +					LOG("WD2010: unknown command %x\n", cmd);
   1.425 +					ctx->status = SR_ERROR;
   1.426 +					ctx->error_reg = ER_ABORTED_COMMAND;
   1.427 +					ctx->irq = true;
   1.428 +					break;
   1.429 +			}
   1.430 +			break;
   1.431 +
   1.432 +	}
   1.433 +}
   1.434 +