src/wd279x.c

Tue, 15 Nov 2011 10:12:37 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Tue, 15 Nov 2011 10:12:37 +0000
changeset 109
2f8afb9e5baa
parent 79
674226015c8a
child 111
4c85846b09cd
permissions
-rw-r--r--

[musashi] Fix handling of bus errors

Patch-Author: Andrew Warkentin <andreww591!gmail>
Patch-MessageID: <4EC200CE.2020304@gmail.com>

I have fixed the first page fault test failure in FreeBee (the page fault test now hangs rather than errors out, because it is trying to read from the hard drive to test DMA page faults).

There were actually two bugs (the first bug was masking the second one).

First, the ancient version of Musashi that you used is unable to properly resume from bus errors that happen in the middle of certain instructions (some instructions are fetched in stages, with the PC being advanced to each part of the instruction, so basically what happens is the CPU core attempts to read the memory location referenced by the first operand, the bus error occurs, causing the PC to jump to the exception vector, but the faulting instruction is still in the middle of being fetched, so the PC is then advanced past the beginning of the exception handler). I fixed this by delaying the jump to the bus error vector until after the faulting instruction finishes.

The second bug is simpler - you had the UDS and LDS bits in BSR0 inverted (they are supposed to be active low).

     1 #include <stdint.h>
     2 #include <stdbool.h>
     3 #include <malloc.h>
     4 #include "musashi/m68k.h"
     5 #include "wd279x.h"
     7 #ifndef WD279X_DEBUG
     8 #define NDEBUG
     9 #endif
    10 #include "utils.h"
    12 /// WD2797 command constants
    13 enum {
    14 	CMD_MASK				= 0xF0,		///< Bit mask to detect command bits
    15 	CMD_RESTORE				= 0x00,		///< Restore (recalibrate, seek to track 0)
    16 	CMD_SEEK				= 0x10,		///< Seek to given track
    17 	CMD_STEP				= 0x20,		///< Step
    18 	CMD_STEP_TU				= 0x30,		///< Step and update track register
    19 	CMD_STEPIN				= 0x40,		///< Step In
    20 	CMD_STEPIN_TU			= 0x50,		///< Step In and update track register
    21 	CMD_STEPOUT				= 0x60,		///< Step Out
    22 	CMD_STEPOUT_TU			= 0x70,		///< Step Out and update track register
    23 	CMD_READ_SECTOR			= 0x80,		///< Read Sector
    24 	CMD_READ_SECTOR_MULTI	= 0x90,		///< Read Multiple Sectors
    25 	CMD_WRITE_SECTOR		= 0xA0,		///< Write Sector
    26 	CMD_WRITE_SECTOR_MULTI	= 0xB0,		///< Write Multiple Sectors
    27 	CMD_READ_ADDRESS		= 0xC0,		///< Read Address (IDAM contents)
    28 	CMD_FORCE_INTERRUPT		= 0xD0,		///< Force Interrupt
    29 	CMD_READ_TRACK			= 0xE0,		///< Read Track
    30 	CMD_FORMAT_TRACK		= 0xF0		///< Format Track
    31 };
    34 void wd2797_init(WD2797_CTX *ctx)
    35 {
    36 	// track, head and sector unknown
    37 	ctx->track = ctx->head = ctx->sector = 0;
    39 	// no IRQ pending
    40 	ctx->irq = false;
    42 	// no data available
    43 	ctx->data_pos = ctx->data_len = 0;
    44 	ctx->data = NULL;
    46 	// Status register clear, not busy; type1 command
    47 	ctx->status = 0;
    48 	ctx->cmd_has_drq = false;
    50 	// Clear data register
    51 	ctx->data_reg = 0;
    53 	// Last step direction = "towards zero"
    54 	ctx->last_step_dir = -1;
    56 	// No disc image loaded
    57 	ctx->disc_image = NULL;
    58 	ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = ctx->geom_tracks = 0;
    59 }
    62 void wd2797_reset(WD2797_CTX *ctx)
    63 {
    64 	// track, head and sector unknown
    65 	ctx->track = ctx->head = ctx->sector = 0;
    67 	// no IRQ pending
    68 	ctx->irq = false;
    70 	// no data available
    71 	ctx->data_pos = ctx->data_len = 0;
    73 	// Status register clear, not busy
    74 	ctx->status = 0;
    76 	// Clear data register
    77 	ctx->data_reg = 0;
    79 	// Last step direction
    80 	ctx->last_step_dir = -1;
    81 }
    84 void wd2797_done(WD2797_CTX *ctx)
    85 {
    86 	// Reset the WD2797
    87 	wd2797_reset(ctx);
    89 	// Free any allocated memory
    90 	if (ctx->data) {
    91 		free(ctx->data);
    92 		ctx->data = NULL;
    93 	}
    94 }
    97 bool wd2797_get_irq(WD2797_CTX *ctx)
    98 {
    99 	return ctx->irq;
   100 }
   103 bool wd2797_get_drq(WD2797_CTX *ctx)
   104 {
   105 	return (ctx->data_pos < ctx->data_len);
   106 }
   109 WD2797_ERR wd2797_load(WD2797_CTX *ctx, FILE *fp, int secsz, int spt, int heads)
   110 {
   111 	size_t filesize;
   113 	// Start by finding out how big the image file is
   114 	fseek(fp, 0, SEEK_END);
   115 	filesize = ftell(fp);
   116 	fseek(fp, 0, SEEK_SET);
   118 	// Now figure out how many tracks it contains
   119 	int tracks = filesize / secsz / spt / heads;
   120 	// Confirm...
   121 	if (tracks < 1) {
   122 		return WD2797_ERR_BAD_GEOM;
   123 	}
   125 	// Allocate enough memory to store one disc track
   126 	if (ctx->data) {
   127 		free(ctx->data);
   128 	}
   129 	ctx->data = malloc(secsz * spt);
   130 	if (!ctx->data)
   131 		return WD2797_ERR_NO_MEMORY;
   133 	// Load the image and the geometry data
   134 	ctx->disc_image = fp;
   135 	ctx->geom_tracks = tracks;
   136 	ctx->geom_secsz = secsz;
   137 	ctx->geom_heads = heads;
   138 	ctx->geom_spt = spt;
   140 	return WD2797_ERR_OK;
   141 }
   144 void wd2797_unload(WD2797_CTX *ctx)
   145 {
   146 	// Free memory buffer
   147 	if (ctx->data) {
   148 		free(ctx->data);
   149 		ctx->data = NULL;
   150 	}
   152 	// Clear file pointer
   153 	ctx->disc_image = NULL;
   155 	// Clear the disc geometry
   156 	ctx->geom_tracks = ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = 0;
   157 }
   160 uint8_t wd2797_read_reg(WD2797_CTX *ctx, uint8_t addr)
   161 {
   162 	uint8_t temp = 0;
   164 	switch (addr & 0x03) {
   165 		case WD2797_REG_STATUS:		// Status register
   166 			// Read from status register clears IRQ
   167 			ctx->irq = false;
   169 			// Get current status flags (set by last command)
   170 			// DRQ bit
   171 			if (ctx->cmd_has_drq) {
   172 				temp = ctx->status & ~0x03;
   173 				temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   174 				LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
   175 			} else {
   176 				temp = ctx->status & ~0x01;
   177 			}
   178 			// FDC is busy if there is still data in the buffer
   179 			temp |= (ctx->data_pos < ctx->data_len) ? 0x01 : 0x00;	// if data in buffer, then DMA hasn't copied it yet, and we're still busy!
   180 																	// TODO: also if seek delay / read delay hasn't passed (but that's for later)
   181 			return temp;
   183 		case WD2797_REG_TRACK:		// Track register
   184 			return ctx->track;
   186 		case WD2797_REG_SECTOR:		// Sector register
   187 			return ctx->sector;
   189 		case WD2797_REG_DATA:		// Data register
   190 			// If there's data in the buffer, return it. Otherwise return 0xFF.
   191 			if (ctx->data_pos < ctx->data_len) {
   192 				// set IRQ if this is the last data byte
   193 				if (ctx->data_pos == (ctx->data_len-1)) {
   194 					// Set IRQ
   195 					ctx->irq = true;
   196 				}
   197 				// return data byte and increment pointer
   198 				return ctx->data[ctx->data_pos++];
   199 			} else {
   200 				// command finished
   201 				return 0xff;
   202 			}
   204 		default:
   205 			// shut up annoying compilers which don't recognise unreachable code when they see it
   206 			// (here's looking at you, gcc!)
   207 			return 0xff;
   208 	}
   209 }
   212 void wd2797_write_reg(WD2797_CTX *ctx, uint8_t addr, uint8_t val)
   213 {
   214 	uint8_t cmd = val & CMD_MASK;
   215 	size_t lba;
   216 	bool is_type1 = false;
   217 	int temp;
   219 	m68k_end_timeslice();
   221 	switch (addr) {
   222 		case WD2797_REG_COMMAND:	// Command register
   223 			// write to command register clears interrupt request
   224 			ctx->irq = false;
   226 			// Is the drive ready?
   227 			if (ctx->disc_image == NULL) {
   228 				// No disc image, thus the drive is busy.
   229 				ctx->status = 0x80;
   230 				return;
   231 			}
   233 			// Handle Type 1 commands
   234 			switch (cmd) {
   235 				case CMD_RESTORE:
   236 					// Restore. Set track to 0 and throw an IRQ.
   237 					is_type1 = true;
   238 					ctx->track = 0;
   239 					break;
   241 				case CMD_SEEK:
   242 					// Seek. Seek to the track specced in the Data Register.
   243 					is_type1 = true;
   244 					if (ctx->data_reg < ctx->geom_tracks) {
   245 						ctx->track = ctx->data_reg;
   246 					} else {
   247 						// Seek error. :(
   248 						ctx->status = 0x10;
   249 					}
   251 				case CMD_STEP:
   252 					// TODO! deal with trk0!
   253 					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   254 					is_type1 = true;
   255 					break;
   257 				case CMD_STEPIN:
   258 				case CMD_STEPOUT:
   259 					// TODO! deal with trk0!
   260 					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   261 					if (cmd == CMD_STEPIN) {
   262 						ctx->last_step_dir = 1;
   263 					} else {
   264 						ctx->last_step_dir = -1;
   265 					}
   266 					is_type1 = true;
   267 					break;
   269 				case CMD_STEP_TU:
   270 				case CMD_STEPIN_TU:
   271 				case CMD_STEPOUT_TU:
   272 					// if this is a Step In or Step Out cmd, set the step-direction
   273 					if (cmd == CMD_STEPIN_TU) {
   274 						ctx->last_step_dir = 1;
   275 					} else if (cmd == CMD_STEPOUT_TU) {
   276 						ctx->last_step_dir = -1;
   277 					}
   279 					// Seek one step in the last direction used.
   280 					ctx->track += ctx->last_step_dir;
   281 					if (ctx->track < 0) ctx->track = 0;
   282 					if (ctx->track >= ctx->geom_tracks) {
   283 						// Seek past end of disc... that'll be a Seek Error then.
   284 						ctx->status = 0x10;
   285 						ctx->track = ctx->geom_tracks - 1;
   286 					}
   287 					is_type1 = true;
   288 					break;
   290 				default:
   291 					break;
   292 			}
   294 			if (is_type1) {
   295 				// Terminate any sector reads or writes
   296 				ctx->data_len = ctx->data_pos = 0;
   298 				// No DRQ bit for these commands.
   299 				ctx->cmd_has_drq = false;
   301 				// Type1 status byte...
   302 				ctx->status = 0;
   303 				// S7 = Not Ready. Command executed, therefore the drive was ready... :)
   304 				// S6 = Write Protect. TODO: add this
   305 				// S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
   306 				ctx->status |= 0x20;
   307 				// S4 = Seek Error. Not bloody likely if we got down here...!
   308 				// S3 = CRC Error. Not gonna happen on a disc image!
   309 				// S2 = Track 0
   310 				ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
   311 				// S1 = Index Pulse. TODO -- need periodics to emulate this
   312 				// S0 = Busy. We just exec'd the command, thus we're not busy.
   313 				// 		TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
   315 				// Set IRQ
   316 				ctx->irq = true;
   317 				return;
   318 			}
   320 			// That's the Type 1 (seek) commands sorted. Now for the others.
   322 			// All these commands return the DRQ bit...
   323 			ctx->cmd_has_drq = true;
   325 			// If drive isn't ready, then set status B7 and exit
   326 			if (ctx->disc_image == NULL) {
   327 				ctx->status = 0x80;
   328 				return;
   329 			}
   331 			// If this is a Write command, check write protect status too
   332 			// TODO!
   333 			if (false) {
   334 				// Write protected disc...
   335 				if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
   336 					// Set Write Protect bit and bail.
   337 					ctx->status = 0x40;
   339 					// Set IRQ
   340 					ctx->irq = true;
   342 					return;
   343 				}
   344 			}
   346 			// Disc is ready to go. Parse the command word.
   347 			switch (cmd) {
   348 				case CMD_READ_ADDRESS:
   349 					// Read Address
   350 					ctx->head = (val & 0x02) ? 1 : 0;
   352 					// reset data pointers
   353 					ctx->data_pos = ctx->data_len = 0;
   355 					// load data buffer
   356 					ctx->data[ctx->data_len++] = ctx->track;
   357 					ctx->data[ctx->data_len++] = ctx->head;
   358 					ctx->data[ctx->data_len++] = ctx->sector;
   359 					switch (ctx->geom_secsz) {
   360 						case 128:	ctx->data[ctx->data_len++] = 0; break;
   361 						case 256:	ctx->data[ctx->data_len++] = 1; break;
   362 						case 512:	ctx->data[ctx->data_len++] = 2; break;
   363 						case 1024:	ctx->data[ctx->data_len++] = 3; break;
   364 						default:	ctx->data[ctx->data_len++] = 0xFF; break;	// TODO: deal with invalid values better
   365 					}
   366 					ctx->data[ctx->data_len++] = 0;	// TODO: IDAM CRC!
   367 					ctx->data[ctx->data_len++] = 0;
   369 					ctx->status = 0;
   370 					// B6, B5 = 0
   371 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   372 					// B3 = CRC Error. Not possible.
   373 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   374 					// B1 = DRQ. Data request.
   375 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   376 					break;
   378 				case CMD_READ_SECTOR:
   379 				case CMD_READ_SECTOR_MULTI:
   380 					ctx->head = (val & 0x02) ? 1 : 0;
   381 					LOG("WD279X: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   382 					// Read Sector or Read Sector Multiple
   384 					// Check to see if the cyl, hd and sec are valid
   385 					if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt) || (ctx->sector == 0)) {
   386 						LOG("*** WD2797 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   387 								ctx->track, ctx->head, ctx->sector,
   388 								ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
   389 						// CHS parameters exceed limits
   390 						ctx->status = 0x10;		// Record Not Found
   391 						break;
   392 						// Set IRQ
   393 						ctx->irq = true;
   394 					}
   396 					// reset data pointers
   397 					ctx->data_pos = ctx->data_len = 0;
   399 					// Calculate number of sectors to read from disc
   400 					if (cmd == CMD_READ_SECTOR_MULTI)
   401 						temp = ctx->geom_spt;
   402 					else
   403 						temp = 1;
   405 					for (int i=0; i<temp; i++) {
   406 						// Calculate the LBA address of the required sector
   407 						// LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
   408 						lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i) - 1;
   409 						// convert LBA to byte address
   410 						lba *= ctx->geom_secsz;
   411 						LOG("\tREAD lba = %lu", lba);
   413 						// Read the sector from the file
   414 						fseek(ctx->disc_image, lba, SEEK_SET);
   415 						// TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   416 						ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
   417 						LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
   418 					}
   420 					ctx->status = 0;
   421 					// B6 = 0
   422 					// B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
   423 					// B4 = Record Not Found. Basically, the CHS parameters are bullcrap.
   424 					// B3 = CRC Error. Not possible.
   425 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   426 					// B1 = DRQ. Data request.
   427 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   428 					break;
   430 				case CMD_READ_TRACK:
   431 					// Read Track
   432 					// TODO! implement this
   433 					ctx->head = (val & 0x02) ? 1 : 0;
   434 					ctx->status = 0;
   435 					// B6, B5, B4, B3 = 0
   436 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   437 					// B1 = DRQ. Data request.
   438 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   439 					break;
   441 				case CMD_WRITE_SECTOR:
   442 				case CMD_WRITE_SECTOR_MULTI:
   443 					// Write Sector or Write Sector Multiple
   445 					ctx->head = (val & 0x02) ? 1 : 0;
   446 					// reset data pointers
   447 					ctx->data_pos = ctx->data_len = 0;
   449 					// TODO: set "write pending" flag, and write LBA, and go from there.
   451 					ctx->status = 0;
   452 					// B6 = Write Protect. FIXME -- emulate this!
   453 					// B5 = 0
   454 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   455 					// B3 = CRC Error. Not possible.
   456 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   457 					// B1 = DRQ. Data request.
   458 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   459 					break;
   461 				case CMD_FORMAT_TRACK:
   462 					// Write Track (aka Format Track)
   463 					ctx->head = (val & 0x02) ? 1 : 0;
   464 					ctx->status = 0;
   465 					// B6 = Write Protect. FIXME -- emulate this!
   466 					// B5, B4, B3 = 0
   467 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   468 					// B1 = DRQ. Data request.
   469 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   470 					break;
   472 				case CMD_FORCE_INTERRUPT:
   473 					// Force Interrupt...
   474 					// Terminates current operation and sends an interrupt
   475 					// TODO!
   476 					ctx->status = 0;
   477 					ctx->data_pos = ctx->data_len = 0;
   478 					// Set IRQ
   479 					ctx->irq = true;
   480 					break;
   481 			}
   482 			break;
   484 		case WD2797_REG_TRACK:		// Track register
   485 			ctx->track = val;
   486 			break;
   488 		case WD2797_REG_SECTOR:		// Sector register
   489 			ctx->sector = val;
   490 			break;
   492 		case WD2797_REG_DATA:		// Data register
   493 			// Save the value written into the data register
   494 			ctx->data_reg = val;
   496 			// If we're processing a write command, and there's space in the
   497 			// buffer, allow the write.
   498 			if (ctx->data_pos < ctx->data_len) {
   499 				// set IRQ if this is the last data byte
   500 				if (ctx->data_pos == (ctx->data_len-1)) {
   501 					// Set IRQ
   502 					ctx->irq = true;
   503 				}
   505 				// store data byte and increment pointer
   506 				ctx->data[ctx->data_pos++] = val;
   507 			}
   508 			break;
   509 	}
   510 }