src/wd2010.c

Sat, 17 Nov 2012 22:15:23 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Sat, 17 Nov 2012 22:15:23 +0000
changeset 115
da3d10af0711
parent 112
a392eb8f9806
child 116
21521e62007f
permissions
-rw-r--r--

wd2010: use LOGS when logging unformatted strings

     1 #include <stdint.h>
     2 #include <stdbool.h>
     3 #include <malloc.h>
     4 #include "SDL.h"
     5 #include "musashi/m68k.h"
     6 #include "wd2010.h"
     8 #define WD2010_DEBUG
    10 #ifndef WD2010_DEBUG
    11 #define NDEBUG
    12 #endif
    13 #include "utils.h"
    15 #define SEEK_DELAY 30
    17 #define CMD_ENABLE_RETRY 0x01
    18 #define CMD_LONG_MODE 0x02
    19 #define CMD_MULTI_SECTOR 0x04
    20 #define CMD_INTRQ_WHEN_COMPLETE 0x08
    22 #define ER_BAD_BLOCK 0x80
    23 #define ER_CRC 0x40
    24 #define ER_ID_NOT_FOUND 0x10
    25 #define ER_ABORTED_COMMAND 0x04
    26 #define ER_NO_TK0 0x02
    27 #define ER_NO_ADDRESS_MARK 0x01
    29 #define SR_BUSY 0x80
    30 #define SR_READY 0x40
    31 #define SR_WRITE_FAULT 0x20
    32 #define SR_SEEK_COMPLETE 0x10
    33 #define SR_DRQ 0x08
    34 #define SR_CORRECTED 0x04
    35 #define SR_COMMAND_IN_PROGRESS 0x02
    36 #define SR_ERROR 0x01
    38 extern int cpu_log_enabled;
    40 /// WD2010 command constants
    41 enum {
    42 	CMD_MASK				= 0xF0,		///< Bit mask to detect command bits
    43 	CMD_2010_EXT			= 0x00,		///< WD2010 extended commands (compute correction, set parameter)
    44 	CMD_RESTORE				= 0x10,		///< Restore (recalibrate, seek to track 0)
    45 	CMD_READ_SECTOR			= 0x20,		///< Read sector
    46 	CMD_WRITE_SECTOR		= 0x30,		///< Write sector
    47 	CMD_SCAN_ID				= 0x40,		///< Scan ID
    48 	CMD_WRITE_FORMAT		= 0x50,		///< Write format
    49 	CMD_SEEK				= 0x70,		///< Seek to given track
    50 };
    52 int wd2010_init(WD2010_CTX *ctx, FILE *fp, int secsz, int spt, int heads)
    53 {
    54 	long filesize;
    55 	wd2010_reset(ctx);
    56 	// Start by finding out how big the image file is
    57 	fseek(fp, 0, SEEK_END);
    58 	filesize = ftell(fp);
    59 	fseek(fp, 0, SEEK_SET);
    61 	// Now figure out how many tracks it contains
    62 	int tracks = filesize / secsz / spt / heads;
    63 	// Confirm...
    64 	if (tracks < 1) {
    65 		return WD2010_ERR_BAD_GEOM;
    66 	}
    68 	// Allocate enough memory to store one disc track
    69 	if (ctx->data) {
    70 		free(ctx->data);
    71 	}
    72 	ctx->data = malloc(secsz * spt);
    73 	if (!ctx->data)
    74 		return WD2010_ERR_NO_MEMORY;
    76 	// Load the image and the geometry data
    77 	ctx->disc_image = fp;
    78 	ctx->geom_tracks = tracks;
    79 	ctx->geom_secsz = secsz;
    80 	ctx->geom_heads = heads;
    81 	ctx->geom_spt = spt;
    82 	return WD2010_ERR_OK;
    84 }
    86 void wd2010_reset(WD2010_CTX *ctx)
    87 {
    88 	// track, head and sector unknown
    89 	ctx->track = ctx->head = ctx->sector = 0;
    91 	// no IRQ pending
    92 	ctx->irq = false;
    94 	// no data available
    95 	ctx->data_pos = ctx->data_len = 0;
    97 	// Status register clear, not busy
    98 	ctx->status = 0;
   100 	ctx->sector_count = 0;
   101 	ctx->sector_number = 0;
   102 	ctx->cylinder_low_reg = 0;
   103 	ctx->cylinder_high_reg = 0;
   104 	ctx->sdh = 0;
   105 }
   107 void wd2010_done(WD2010_CTX *ctx)
   108 {
   109 	// Reset the WD2010
   110 	wd2010_reset(ctx);
   112 	// Free any allocated memory
   113 	if (ctx->data) {
   114 		free(ctx->data);
   115 		ctx->data = NULL;
   116 	}
   117 }
   120 bool wd2010_get_irq(WD2010_CTX *ctx)
   121 {
   122 	return ctx->irq;
   123 }
   125 bool wd2010_get_drq(WD2010_CTX *ctx)
   126 {
   127 	return (ctx->drq && ctx->data_pos < ctx->data_len);
   128 }
   130 void wd2010_dma_miss(WD2010_CTX *ctx)
   131 {
   132 	ctx->data_pos = ctx->data_len;
   133 	ctx->write_pos = 0;
   134 	ctx->status = SR_READY | SR_SEEK_COMPLETE;
   135 	ctx->irq = true;
   136 }
   138 uint8_t wd2010_read_data(WD2010_CTX *ctx)
   139 {
   140 	// If there's data in the buffer, return it. Otherwise return 0xFF.
   141 	if (ctx->data_pos < ctx->data_len) {
   142 		if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
   143 			ctx->sector_count--;
   144 			ctx->sector_number++;
   145 		}
   146 		// set IRQ if this is the last data byte
   147 		if (ctx->data_pos == (ctx->data_len-1)) {
   148 			ctx->status = SR_READY | SR_SEEK_COMPLETE;
   149 			// Set IRQ
   150 			ctx->irq = true;
   151 			ctx->drq = false;
   152 		}
   153 		// return data byte and increment pointer
   154 		return ctx->data[ctx->data_pos++];
   155 	} else {
   156 		// empty buffer (this shouldn't happen)
   157 		LOGS("WD2010: attempt to read from empty data buffer");
   158 		return 0xff;
   159 	}
   160 }
   162 void wd2010_write_data(WD2010_CTX *ctx, uint8_t val)
   163 {
   164 	// If we're processing a write command, and there's space in the
   165 	// buffer, allow the write.
   166 	if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) {
   167 		// store data byte and increment pointer
   168 		if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
   169 			ctx->sector_count--;
   170 			ctx->sector_number++;
   171 		}
   172 		ctx->data[ctx->data_pos++] = val;
   173 		// set IRQ and write data if this is the last data byte
   174 		if (ctx->data_pos == ctx->data_len) {
   175 			if (!ctx->formatting){
   176 				fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
   177 				fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
   178 				fflush(ctx->disc_image);
   179 			}
   180 			ctx->formatting = false;
   181 			ctx->status = SR_READY | SR_SEEK_COMPLETE;
   182 			// Set IRQ and reset write pointer
   183 			ctx->irq = true;
   184 			ctx->write_pos = -1;
   185 			ctx->drq = false;
   186 		}
   187 	}else{
   188 		LOGS("WD2010: attempt to write to data buffer without a write command in progress");
   189 	}
   190 }
   192 uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx)
   193 {
   194 	/*m68k_end_timeslice();*/
   195 	ctx->status = SR_READY | SR_SEEK_COMPLETE;
   196 	ctx->irq = true;
   197 	return (0);
   198 }
   200 uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx)
   201 {
   202 	/*m68k_end_timeslice();*/
   203 	ctx->drq = true;
   204 	return (0);
   205 }
   207 uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr)
   208 {
   209 	uint8_t temp = 0;
   211 	/*cpu_log_enabled = 1;*/
   213 	switch (addr & 0x07) {
   214 		case WD2010_REG_ERROR:
   215 			return ctx->error_reg;
   216 		case WD2010_REG_SECTOR_COUNT:
   217 			return ctx->sector_count;
   218 		case WD2010_REG_SECTOR_NUMBER:
   219 			return ctx->sector_number;
   220 		case WD2010_REG_CYLINDER_HIGH:      // High byte of cylinder
   221 			return ctx->cylinder_high_reg;
   222 		case WD2010_REG_CYLINDER_LOW:       // Low byte of cylinder
   223 			return ctx->cylinder_low_reg;
   224 		case WD2010_REG_SDH:
   225 			return ctx->sdh;
   226 		case WD2010_REG_STATUS:             // Status register
   227 			// Read from status register clears IRQ
   228 			ctx->irq = false;
   229 			// Get current status flags (set by last command)
   230 			// DRQ bit
   231 			if (ctx->cmd_has_drq) {
   232 				temp = ctx->status & ~(SR_BUSY & SR_DRQ);
   233 				temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0;
   234 				LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
   235 			} else {
   236 				temp = ctx->status & ~0x80;
   237 			}
   238 			/*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/
   239 			// HDC is busy if there is still data in the buffer
   240 			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!
   241 																	// TODO: also if seek delay / read delay hasn't passed (but that's for later)
   242 			/*XXX: should anything else be set here?*/
   243 			return temp;
   244 		default:
   245 			// shut up annoying compilers which don't recognise unreachable code when they see it
   246 			// (here's looking at you, gcc!)
   247 			return 0xff;
   248 	}
   249 }
   252 void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val)
   253 {
   254 	uint8_t cmd = val & CMD_MASK;
   255 	size_t lba;
   256 	int new_track;
   257 	int sector_count;
   259 	m68k_end_timeslice();
   261 	/*cpu_log_enabled = 1;*/
   263 	switch (addr & 0x07) {
   264 		case WD2010_REG_WRITE_PRECOMP_CYLINDER:
   265 			break;
   266 		case WD2010_REG_SECTOR_COUNT:
   267 			ctx->sector_count = val;
   268 			break;
   269 		case WD2010_REG_SECTOR_NUMBER:
   270 			ctx->sector_number = val;
   271 			break;
   272 		case WD2010_REG_CYLINDER_HIGH:		// High byte of cylinder
   273 			ctx->cylinder_high_reg = val;
   274 			break;
   275 		case WD2010_REG_CYLINDER_LOW:		// Low byte of cylinder
   276 			ctx->cylinder_low_reg = val;
   277 			break;
   278 		case WD2010_REG_SDH:
   279 			/*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/
   280 			ctx->data_pos = ctx->data_len = 0;
   281 			ctx->sdh = val;
   282 			break;
   283 		case WD2010_REG_COMMAND:	// Command register
   284 			// write to command register clears interrupt request
   285 			ctx->irq = false;
   286 			ctx->error_reg = 0;
   288 			/*cpu_log_enabled = 1;*/
   289 			switch (cmd) {
   290 				case CMD_RESTORE:
   291 					// Restore. Set track to 0 and throw an IRQ.
   292 					ctx->track = 0;
   293 					SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
   294 					break;
   295 				case CMD_SCAN_ID:
   296 					ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff;
   297 					ctx->cylinder_low_reg = ctx->track & 0xff;
   298 					ctx->sector_number = ctx->sector;
   299 					ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7);
   300 				case CMD_WRITE_FORMAT:
   301 				case CMD_SEEK:
   302 				case CMD_READ_SECTOR:
   303 				case CMD_WRITE_SECTOR:
   304 					// Seek. Seek to the track specced in the cylinder
   305 					// registers.
   306 					new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg;
   307 					if (new_track < ctx->geom_tracks) {
   308 						ctx->track = new_track;
   309 					} else {
   310 						// Seek error. :(
   311 						ctx->status = SR_ERROR;
   312 						ctx->error_reg = ER_ID_NOT_FOUND;
   313 						ctx->irq = true;
   314 						break;
   315 					}
   316 					ctx->head = ctx->sdh & 0x07;
   317 					ctx->sector = ctx->sector_number;
   319 					ctx->formatting = cmd == CMD_WRITE_FORMAT;
   320 					switch (cmd){
   321 						case CMD_SEEK:
   322 							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
   323 							break;
   324 						case CMD_READ_SECTOR:
   325 							/*XXX: does a separate function to set the head have to be added?*/
   326 							LOG("WD2010: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   328 							// Read Sector
   330 							// Check to see if the cyl, hd and sec are valid
   331 							if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt - 1)) {
   332 								LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   333 										ctx->track, ctx->head, ctx->sector,
   334 										ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt-1);
   335 								// CHS parameters exceed limits
   336 								ctx->status = SR_ERROR;
   337 								ctx->error_reg = ER_ID_NOT_FOUND;
   338 								// Set IRQ
   339 								ctx->irq = true;
   340 								break;
   341 							}
   343 							// reset data pointers
   344 							ctx->data_pos = ctx->data_len = 0;
   346 							if (val & CMD_MULTI_SECTOR){
   347 								ctx->multi_sector = 1;
   348 								sector_count = ctx->sector_count;
   349 							}else{
   350 								ctx->multi_sector = 0;
   351 								sector_count = 1;
   352 							}
   353 							for (int i=0; i<sector_count; i++) {
   354 								// Calculate the LBA address of the required sector
   355 								// LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
   356 								lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i);
   357 								// convert LBA to byte address
   358 								lba *= ctx->geom_secsz;
   359 								LOG("\tREAD lba = %lu", lba);
   361 								// Read the sector from the file
   362 								fseek(ctx->disc_image, lba, SEEK_SET);
   363 								// TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   364 								ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
   365 								LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
   366 							}
   368 							ctx->status = 0;
   369 							ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
   370 							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
   372 							break;
   373 						case CMD_WRITE_FORMAT:
   374 							ctx->sector = 0;
   375 						case CMD_WRITE_SECTOR:
   376 							LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   377 							// Read Sector
   379 							// Check to see if the cyl, hd and sec are valid
   380 							if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt-1)) {
   381 								LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   382 										ctx->track, ctx->head, ctx->sector,
   383 										ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
   384 								// CHS parameters exceed limits
   385 								ctx->status = SR_ERROR;
   386 								ctx->error_reg = ER_ID_NOT_FOUND;
   387 								// Set IRQ
   388 								ctx->irq = true;
   389 								break;
   390 							}
   392 							// reset data pointers
   393 							ctx->data_pos = ctx->data_len = 0;
   395 							if (val & CMD_MULTI_SECTOR){
   396 								ctx->multi_sector = 1;
   397 								sector_count = ctx->sector_count;
   398 							}else{
   399 								ctx->multi_sector = 0;
   400 								sector_count = 1;
   401 							}
   402 							ctx->data_len = ctx->geom_secsz * sector_count;
   403 							lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector));
   404 							// convert LBA to byte address
   405 							ctx->write_pos = lba * ctx->geom_secsz;
   406 							LOG("\tWRITE lba = %lu", lba);
   408 							ctx->status = 0;
   409 							ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
   410 							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
   412 							break;
   413 						default:
   414 							LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd);
   415 							break;
   416 					}
   417 					break;
   418 				case CMD_2010_EXT: /* not implemented */
   419 				default:
   420 					LOG("WD2010: unknown command %x\n", cmd);
   421 					ctx->status = SR_ERROR;
   422 					ctx->error_reg = ER_ABORTED_COMMAND;
   423 					ctx->irq = true;
   424 					break;
   425 			}
   426 			break;
   428 	}
   429 }