src/wd2010.c

Wed, 16 Jan 2013 00:34:11 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Wed, 16 Jan 2013 00:34:11 +0000
changeset 122
b214cf455ff2
parent 116
21521e62007f
child 123
82755a8d276e
permissions
-rw-r--r--

[wd2010] use size_t for init filesize, make disc init more verbose

     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 	size_t filesize;
    56 	wd2010_reset(ctx);
    58 	// Start by finding out how big the image file is
    59 	fseek(fp, 0, SEEK_END);
    60 	filesize = ftell(fp);
    61 	fseek(fp, 0, SEEK_SET);
    63 	// Now figure out how many tracks it contains
    64 	unsigned int tracks = filesize / secsz / spt / heads;
    65 	// Confirm...
    66 	if (tracks < 1) {
    67 		return WD2010_ERR_BAD_GEOM;
    68 	}
    70 	LOG("WD2010 initialised, %d cylinders, %d heads, %d sectors per track", tracks, heads, spt);
    72 	// Allocate enough memory to store one disc track
    73 	if (ctx->data) {
    74 		free(ctx->data);
    75 	}
    76 	ctx->data = malloc(secsz * spt);
    77 	if (!ctx->data)
    78 		return WD2010_ERR_NO_MEMORY;
    80 	// Load the image and the geometry data
    81 	ctx->disc_image = fp;
    82 	ctx->geom_tracks = tracks;
    83 	ctx->geom_secsz = secsz;
    84 	ctx->geom_heads = heads;
    85 	ctx->geom_spt = spt;
    87 	return WD2010_ERR_OK;
    88 }
    90 void wd2010_reset(WD2010_CTX *ctx)
    91 {
    92 	// track, head and sector unknown
    93 	ctx->track = ctx->head = ctx->sector = 0;
    95 	// no IRQ pending
    96 	ctx->irq = false;
    98 	// no data available
    99 	ctx->data_pos = ctx->data_len = 0;
   101 	// Status register clear, not busy
   102 	ctx->status = 0;
   104 	ctx->sector_count = 0;
   105 	ctx->sector_number = 0;
   106 	ctx->cylinder_low_reg = 0;
   107 	ctx->cylinder_high_reg = 0;
   108 	ctx->sdh = 0;
   109 	ctx->mcr2_hdsel3 = 0;
   110 	ctx->mcr2_ddrive1 = 0;
   111 }
   113 void wd2010_done(WD2010_CTX *ctx)
   114 {
   115 	// Reset the WD2010
   116 	wd2010_reset(ctx);
   118 	// Free any allocated memory
   119 	if (ctx->data) {
   120 		free(ctx->data);
   121 		ctx->data = NULL;
   122 	}
   123 }
   126 bool wd2010_get_irq(WD2010_CTX *ctx)
   127 {
   128 	return ctx->irq;
   129 }
   131 bool wd2010_get_drq(WD2010_CTX *ctx)
   132 {
   133 	return (ctx->drq && ctx->data_pos < ctx->data_len);
   134 }
   136 void wd2010_dma_miss(WD2010_CTX *ctx)
   137 {
   138 	ctx->data_pos = ctx->data_len;
   139 	ctx->write_pos = 0;
   140 	ctx->status = SR_READY | SR_SEEK_COMPLETE;
   141 	ctx->irq = true;
   142 }
   144 uint8_t wd2010_read_data(WD2010_CTX *ctx)
   145 {
   146 	// If there's data in the buffer, return it. Otherwise return 0xFF.
   147 	if (ctx->data_pos < ctx->data_len) {
   148 		if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
   149 			ctx->sector_count--;
   150 			ctx->sector_number++;
   151 		}
   152 		// set IRQ if this is the last data byte
   153 		if (ctx->data_pos == (ctx->data_len-1)) {
   154 			ctx->status = SR_READY | SR_SEEK_COMPLETE;
   155 			// Set IRQ
   156 			ctx->irq = true;
   157 			ctx->drq = false;
   158 		}
   159 		// return data byte and increment pointer
   160 		return ctx->data[ctx->data_pos++];
   161 	} else {
   162 		// empty buffer (this shouldn't happen)
   163 		LOGS("WD2010: attempt to read from empty data buffer");
   164 		return 0xff;
   165 	}
   166 }
   168 void wd2010_write_data(WD2010_CTX *ctx, uint8_t val)
   169 {
   170 	// If we're processing a write command, and there's space in the
   171 	// buffer, allow the write.
   172 	if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) {
   173 		// store data byte and increment pointer
   174 		if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
   175 			ctx->sector_count--;
   176 			ctx->sector_number++;
   177 		}
   178 		ctx->data[ctx->data_pos++] = val;
   179 		// set IRQ and write data if this is the last data byte
   180 		if (ctx->data_pos == ctx->data_len) {
   181 			if (!ctx->formatting){
   182 				fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
   183 				fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
   184 				fflush(ctx->disc_image);
   185 			}
   186 			ctx->formatting = false;
   187 			ctx->status = SR_READY | SR_SEEK_COMPLETE;
   188 			// Set IRQ and reset write pointer
   189 			ctx->irq = true;
   190 			ctx->write_pos = -1;
   191 			ctx->drq = false;
   192 		}
   193 	}else{
   194 		LOGS("WD2010: attempt to write to data buffer without a write command in progress");
   195 	}
   196 }
   198 uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx)
   199 {
   200 	/*m68k_end_timeslice();*/
   201 	ctx->status = SR_READY | SR_SEEK_COMPLETE;
   202 	ctx->irq = true;
   203 	return (0);
   204 }
   206 uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx)
   207 {
   208 	/*m68k_end_timeslice();*/
   209 	ctx->drq = true;
   210 	return (0);
   211 }
   213 uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr)
   214 {
   215 	uint8_t temp = 0;
   217 	/*cpu_log_enabled = 1;*/
   219 	switch (addr & 0x07) {
   220 		case WD2010_REG_ERROR:
   221 			return ctx->error_reg;
   222 		case WD2010_REG_SECTOR_COUNT:
   223 			return ctx->sector_count;
   224 		case WD2010_REG_SECTOR_NUMBER:
   225 			return ctx->sector_number;
   226 		case WD2010_REG_CYLINDER_HIGH:      // High byte of cylinder
   227 			return ctx->cylinder_high_reg;
   228 		case WD2010_REG_CYLINDER_LOW:       // Low byte of cylinder
   229 			return ctx->cylinder_low_reg;
   230 		case WD2010_REG_SDH:
   231 			return ctx->sdh;
   232 		case WD2010_REG_STATUS:             // Status register
   233 			// Read from status register clears IRQ
   234 			ctx->irq = false;
   235 			// Get current status flags (set by last command)
   236 			// DRQ bit
   237 			if (ctx->cmd_has_drq) {
   238 				temp = ctx->status & ~(SR_BUSY & SR_DRQ);
   239 				temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0;
   240 				LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
   241 			} else {
   242 				temp = ctx->status & ~0x80;
   243 			}
   244 			/*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/
   245 			// HDC is busy if there is still data in the buffer
   246 			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!
   247 																	// TODO: also if seek delay / read delay hasn't passed (but that's for later)
   248 			/*XXX: should anything else be set here?*/
   249 			return temp;
   250 		default:
   251 			// shut up annoying compilers which don't recognise unreachable code when they see it
   252 			// (here's looking at you, gcc!)
   253 			return 0xff;
   254 	}
   255 }
   258 void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val)
   259 {
   260 	uint8_t cmd = val & CMD_MASK;
   261 	size_t lba;
   262 	int new_track;
   263 	int sector_count;
   265 	m68k_end_timeslice();
   267 	/*cpu_log_enabled = 1;*/
   269 	if (addr == UNIXPC_REG_MCR2) {
   270 		// The UNIX PC has an "MCR2" register with the following format:
   271 		//   [ 7..2 ][1][0]
   272 		//   Bits 7..2: Not used
   273 		//   Bit 1:     DDRIVE1 (hard disk drive 1 select - not used?)
   274 		//   Bit 0:     HDSEL3  (head-select bit 3)
   275 		ctx->mcr2_hdsel3 = ((val & 1) == 1);
   276 		ctx->mcr2_ddrive1 = ((val & 2) == 2);
   277 		return;
   278 	}
   280 	switch (addr & 0x07) {
   281 		case WD2010_REG_WRITE_PRECOMP_CYLINDER:
   282 			break;
   283 		case WD2010_REG_SECTOR_COUNT:
   284 			ctx->sector_count = val;
   285 			break;
   286 		case WD2010_REG_SECTOR_NUMBER:
   287 			ctx->sector_number = val;
   288 			break;
   289 		case WD2010_REG_CYLINDER_HIGH:		// High byte of cylinder
   290 			ctx->cylinder_high_reg = val;
   291 			break;
   292 		case WD2010_REG_CYLINDER_LOW:		// Low byte of cylinder
   293 			ctx->cylinder_low_reg = val;
   294 			break;
   295 		case WD2010_REG_SDH:
   296 			/*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/
   297 			ctx->data_pos = ctx->data_len = 0;
   298 			ctx->sdh = val;
   299 			break;
   300 		case WD2010_REG_COMMAND:	// Command register
   301 			// write to command register clears interrupt request
   302 			ctx->irq = false;
   303 			ctx->error_reg = 0;
   305 			/*cpu_log_enabled = 1;*/
   306 			switch (cmd) {
   307 				case CMD_RESTORE:
   308 					// Restore. Set track to 0 and throw an IRQ.
   309 					ctx->track = 0;
   310 					SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
   311 					break;
   312 				case CMD_SCAN_ID:
   313 					ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff;
   314 					ctx->cylinder_low_reg = ctx->track & 0xff;
   315 					ctx->sector_number = ctx->sector;
   316 					ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7);
   317 				case CMD_WRITE_FORMAT:
   318 				case CMD_SEEK:
   319 				case CMD_READ_SECTOR:
   320 				case CMD_WRITE_SECTOR:
   321 					// Seek. Seek to the track specced in the cylinder
   322 					// registers.
   323 					new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg;
   324 					if (new_track < ctx->geom_tracks) {
   325 						ctx->track = new_track;
   326 					} else {
   327 						// Seek error. :(
   328 						ctx->status = SR_ERROR;
   329 						ctx->error_reg = ER_ID_NOT_FOUND;
   330 						ctx->irq = true;
   331 						break;
   332 					}
   333 					// The SDH register provides 3 head select bits; the 4th comes from MCR2.
   334 					ctx->head = (ctx->sdh & 0x07) + (ctx->mcr2_hdsel3 ? 8 : 0);
   335 					ctx->sector = ctx->sector_number;
   337 					ctx->formatting = cmd == CMD_WRITE_FORMAT;
   338 					switch (cmd){
   339 						case CMD_SEEK:
   340 							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
   341 							break;
   342 						case CMD_READ_SECTOR:
   343 							/*XXX: does a separate function to set the head have to be added?*/
   344 							LOG("WD2010: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   346 							// Read Sector
   348 							// Check to see if the cyl, hd and sec are valid
   349 							if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt - 1)) {
   350 								LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   351 										ctx->track, ctx->head, ctx->sector,
   352 										ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt-1);
   353 								// CHS parameters exceed limits
   354 								ctx->status = SR_ERROR;
   355 								ctx->error_reg = ER_ID_NOT_FOUND;
   356 								// Set IRQ
   357 								ctx->irq = true;
   358 								break;
   359 							}
   361 							// reset data pointers
   362 							ctx->data_pos = ctx->data_len = 0;
   364 							if (val & CMD_MULTI_SECTOR){
   365 								ctx->multi_sector = 1;
   366 								sector_count = ctx->sector_count;
   367 							}else{
   368 								ctx->multi_sector = 0;
   369 								sector_count = 1;
   370 							}
   371 							for (int i=0; i<sector_count; i++) {
   372 								// Calculate the LBA address of the required sector
   373 								// LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
   374 								lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i);
   375 								// convert LBA to byte address
   376 								lba *= ctx->geom_secsz;
   377 								LOG("\tREAD lba = %lu", lba);
   379 								// Read the sector from the file
   380 								fseek(ctx->disc_image, lba, SEEK_SET);
   381 								// TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   382 								ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
   383 								LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
   384 							}
   386 							ctx->status = 0;
   387 							ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
   388 							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
   390 							break;
   391 						case CMD_WRITE_FORMAT:
   392 							ctx->sector = 0;
   393 						case CMD_WRITE_SECTOR:
   394 							LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   395 							// Read Sector
   397 							// Check to see if the cyl, hd and sec are valid
   398 							if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt-1)) {
   399 								LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   400 										ctx->track, ctx->head, ctx->sector,
   401 										ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
   402 								// CHS parameters exceed limits
   403 								ctx->status = SR_ERROR;
   404 								ctx->error_reg = ER_ID_NOT_FOUND;
   405 								// Set IRQ
   406 								ctx->irq = true;
   407 								break;
   408 							}
   410 							// reset data pointers
   411 							ctx->data_pos = ctx->data_len = 0;
   413 							if (val & CMD_MULTI_SECTOR){
   414 								ctx->multi_sector = 1;
   415 								sector_count = ctx->sector_count;
   416 							}else{
   417 								ctx->multi_sector = 0;
   418 								sector_count = 1;
   419 							}
   420 							ctx->data_len = ctx->geom_secsz * sector_count;
   421 							lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector));
   422 							// convert LBA to byte address
   423 							ctx->write_pos = lba * ctx->geom_secsz;
   424 							LOG("\tWRITE lba = %lu", lba);
   426 							ctx->status = 0;
   427 							ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
   428 							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
   430 							break;
   431 						default:
   432 							LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd);
   433 							break;
   434 					}
   435 					break;
   436 				case CMD_2010_EXT: /* not implemented */
   437 				default:
   438 					LOG("WD2010: unknown command %x\n", cmd);
   439 					ctx->status = SR_ERROR;
   440 					ctx->error_reg = ER_ABORTED_COMMAND;
   441 					ctx->irq = true;
   442 					break;
   443 			}
   444 			break;
   446 	}
   447 }