src/wd2010.c

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