src/wd2010.c

Fri, 12 Apr 2013 16:26:25 +0100

author
Philip Pemberton <philpem@philpem.me.uk>
date
Fri, 12 Apr 2013 16:26:25 +0100
branch
experimental_memory_mapper_v2
changeset 144
609707511166
parent 135
159f937af10d
child 145
2d6de28c6e6c
permissions
-rw-r--r--

Don't set PS1 if there is a level-7 interrupt or bus error

PS1 should only be set if the page was originally present (PS1 or PS0 set). If
PS0 and PS1 are clear (page not present) then do NOT set PS1.

Once again the TRM is blatantly and spectacularly wrong...

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