src/wd2010.c

Thu, 17 Apr 2014 01:50:41 -0600

author
andrew@localhost
date
Thu, 17 Apr 2014 01:50:41 -0600
changeset 148
b0eac383e342
parent 136
f7d78dfb45d0
child 149
1124f0f5409d
permissions
-rw-r--r--

ignore out-of-range addresses on low-level format commands (s4diag formats once sector past the end of each track)

     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=%zu len=%zu, 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;
   267 	m68k_end_timeslice();
   269 	/*cpu_log_enabled = 1;*/
   271 	if (addr == UNIXPC_REG_MCR2) {
   272 		// The UNIX PC has an "MCR2" register with the following format:
   273 		//   [ 7..2 ][1][0]
   274 		//   Bits 7..2: Not used
   275 		//   Bit 1:     DDRIVE1 (hard disk drive 1 select - not used?)
   276 		//   Bit 0:     HDSEL3  (head-select bit 3)
   277 		ctx->mcr2_hdsel3 = ((val & 1) == 1);
   278 		ctx->mcr2_ddrive1 = ((val & 2) == 2);
   279 		return;
   280 	}
   282 	switch (addr & 0x07) {
   283 		case WD2010_REG_WRITE_PRECOMP_CYLINDER:
   284 			break;
   285 		case WD2010_REG_SECTOR_COUNT:
   286 			ctx->sector_count = val;
   287 			break;
   288 		case WD2010_REG_SECTOR_NUMBER:
   289 			ctx->sector_number = val;
   290 			break;
   291 		case WD2010_REG_CYLINDER_HIGH:		// High byte of cylinder
   292 			ctx->cylinder_high_reg = val;
   293 			break;
   294 		case WD2010_REG_CYLINDER_LOW:		// Low byte of cylinder
   295 			ctx->cylinder_low_reg = val;
   296 			break;
   297 		case WD2010_REG_SDH:
   298 			/*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/
   299 			//ctx->data_pos = ctx->data_len = 0;
   300 			ctx->sdh = val;
   301 			break;
   302 		case WD2010_REG_COMMAND:	// Command register
   303 			// write to command register clears interrupt request
   304 			ctx->irq = false;
   305 			ctx->error_reg = 0;
   307 			/*cpu_log_enabled = 1;*/
   308 			switch (cmd) {
   309 				case CMD_RESTORE:
   310 					// Restore. Set track to 0 and throw an IRQ.
   311 					ctx->track = 0;
   312 					SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
   313 					break;
   314 				case CMD_SCAN_ID:
   315 					ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff;
   316 					ctx->cylinder_low_reg = ctx->track & 0xff;
   317 					ctx->sector_number = ctx->sector;
   318 					ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7);
   319 				case CMD_WRITE_FORMAT:
   320 				case CMD_SEEK:
   321 				case CMD_READ_SECTOR:
   322 				case CMD_WRITE_SECTOR:
   323 					// Seek. Seek to the track specced in the cylinder
   324 					// registers.
   325 					new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg;
   326 					if (new_track < ctx->geom_tracks) {
   327 						ctx->track = new_track;
   328 					} else {
   329 						// Seek error. :(
   330 						LOG("WD2010 ALERT: track %d out of range", new_track);
   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 chs=%d:%d:%d nsectors=%d", cmd, ctx->track, ctx->head, ctx->sector, ctx->sector_count);
   349 							// Read Sector
   351 							// Check to see if the cyl, hd and sec are valid
   352 							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))) {
   353 								LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, nSecs=%d, endSec=%d maxCHS=%d:%d:%d",
   354 										ctx->track, ctx->head, ctx->sector,
   355 										ctx->sector_count,
   356 										ctx->sector + ctx->sector_count - 1,
   357 										ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
   358 								// CHS parameters exceed limits
   359 								ctx->status = SR_ERROR;
   360 								ctx->error_reg = ER_ID_NOT_FOUND;
   361 								// Set IRQ
   362 								ctx->irq = true;
   363 								break;
   364 							}
   366 							// reset data pointers
   367 							ctx->data_pos = ctx->data_len = 0;
   369 							if (val & CMD_MULTI_SECTOR){
   370 								ctx->multi_sector = 1;
   371 								sector_count = ctx->sector_count;
   372 							}else{
   373 								ctx->multi_sector = 0;
   374 								sector_count = 1;
   375 							}
   376 							for (int i=0; i<sector_count; i++) {
   377 								// Calculate the LBA address of the required sector
   378 								// LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
   379 								lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i);
   380 								// convert LBA to byte address
   381 								lba *= ctx->geom_secsz;
   382 								LOG("\tREAD lba = %zu", lba);
   384 								// Read the sector from the file
   385 								fseek(ctx->disc_image, lba, SEEK_SET);
   386 								// TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   387 								ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
   388 								LOG("\tREAD len=%zu, pos=%zu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
   389 							}
   391 							ctx->status = 0;
   392 							ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
   393 							SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
   395 							break;
   396 						case CMD_WRITE_FORMAT:
   397 							ctx->sector = 0;
   398 						case CMD_WRITE_SECTOR:
   399 							LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d nsectors=%d", cmd, ctx->track, ctx->head, ctx->sector, ctx->sector_count);
   400 							// Write Sector
   402 							// Check to see if the cyl, hd and sec are valid
   403 							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))) {
   404 								LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, nSecs=%d, endSec=%d maxCHS=%d:%d:%d",
   405 										ctx->track, ctx->head, ctx->sector,
   406 										ctx->sector_count,
   407 										ctx->sector + ctx->sector_count - 1,
   408 										ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
   409 								// CHS parameters exceed limits
   410 								ctx->status = SR_ERROR;
   411 								ctx->error_reg = ER_ID_NOT_FOUND;
   412 								// Set IRQ
   413 								ctx->irq = true;
   414 								break;
   415 							}
   417 							// reset data pointers
   418 							ctx->data_pos = ctx->data_len = 0;
   420 							if (val & CMD_MULTI_SECTOR){
   421 								ctx->multi_sector = 1;
   422 								sector_count = ctx->sector_count;
   423 							}else{
   424 								ctx->multi_sector = 0;
   425 								sector_count = 1;
   426 							}
   427 							ctx->data_len = ctx->geom_secsz * sector_count;
   428 							lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector));
   429 							// convert LBA to byte address
   430 							ctx->write_pos = (lba *= ctx->geom_secsz);
   431 							LOG("\tWRITE lba = %zu", lba);
   433 							ctx->status = 0;
   434 							ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
   435 							SDL_AddTimer(WD2010_SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
   437 							break;
   438 						default:
   439 							LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd);
   440 							break;
   441 					}
   442 					break;
   443 				case CMD_2010_EXT: /* not implemented */
   444 				default:
   445 					LOG("WD2010: unknown command %x\n", cmd);
   446 					ctx->status = SR_ERROR;
   447 					ctx->error_reg = ER_ABORTED_COMMAND;
   448 					ctx->irq = true;
   449 					break;
   450 			}
   451 			break;
   453 	}
   454 }