src/wd279x.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 111
4c85846b09cd
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 "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;
    38 	ctx->track_reg = 0;
    40 	// no IRQ pending
    41 	ctx->irq = false;
    43 	// no data available
    44 	ctx->data_pos = ctx->data_len = 0;
    45 	ctx->data = NULL;
    47 	// Status register clear, not busy; type1 command
    48 	ctx->status = 0;
    49 	ctx->cmd_has_drq = false;
    51 	// No format command in progress
    52 	ctx->formatting = false;
    54 	// Clear data register
    55 	ctx->data_reg = 0;
    57 	// Last step direction = "towards zero"
    58 	ctx->last_step_dir = -1;
    60 	// No disc image loaded
    61 	ctx->disc_image = NULL;
    62 	ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = ctx->geom_tracks = 0;
    63 }
    66 void wd2797_reset(WD2797_CTX *ctx)
    67 {
    68 	// track, head and sector unknown
    69 	ctx->track = ctx->head = ctx->sector = 0;
    70 	ctx->track_reg = 0;
    72 	// no IRQ pending
    73 	ctx->irq = false;
    75 	// no data available
    76 	ctx->data_pos = ctx->data_len = 0;
    78 	// Status register clear, not busy
    79 	ctx->status = 0;
    81 	// Clear data register
    82 	ctx->data_reg = 0;
    84 	// Last step direction
    85 	ctx->last_step_dir = -1;
    86 }
    89 void wd2797_done(WD2797_CTX *ctx)
    90 {
    91 	// Reset the WD2797
    92 	wd2797_reset(ctx);
    94 	// Free any allocated memory
    95 	if (ctx->data) {
    96 		free(ctx->data);
    97 		ctx->data = NULL;
    98 	}
    99 }
   102 bool wd2797_get_irq(WD2797_CTX *ctx)
   103 {
   104 	return ctx->irq;
   105 }
   107 bool wd2797_get_drq(WD2797_CTX *ctx)
   108 {
   109 	return (ctx->data_pos < ctx->data_len);
   110 }
   113 WD2797_ERR wd2797_load(WD2797_CTX *ctx, FILE *fp, int secsz, int spt, int heads, int writeable)
   114 {
   115 	size_t filesize;
   117 	// Start by finding out how big the image file is
   118 	fseek(fp, 0, SEEK_END);
   119 	filesize = ftell(fp);
   120 	fseek(fp, 0, SEEK_SET);
   122 	// Now figure out how many tracks it contains
   123 	int tracks = filesize / secsz / spt / heads;
   124 	// Confirm...
   125 	if (tracks < 1) {
   126 		return WD2797_ERR_BAD_GEOM;
   127 	}
   129 	// Allocate enough memory to store one disc track
   130 	if (ctx->data) {
   131 		free(ctx->data);
   132 	}
   133 	ctx->data = malloc(secsz * spt);
   134 	if (!ctx->data)
   135 		return WD2797_ERR_NO_MEMORY;
   137 	// Load the image and the geometry data
   138 	ctx->disc_image = fp;
   139 	ctx->geom_tracks = tracks;
   140 	ctx->geom_secsz = secsz;
   141 	ctx->geom_heads = heads;
   142 	ctx->geom_spt = spt;
   143 	ctx->writeable = writeable;
   144 	return WD2797_ERR_OK;
   145 }
   148 void wd2797_unload(WD2797_CTX *ctx)
   149 {
   150 	// Free memory buffer
   151 	if (ctx->data) {
   152 		free(ctx->data);
   153 		ctx->data = NULL;
   154 	}
   156 	// Clear file pointer
   157 	ctx->disc_image = NULL;
   159 	// Clear the disc geometry
   160 	ctx->geom_tracks = ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = 0;
   161 }
   164 uint8_t wd2797_read_reg(WD2797_CTX *ctx, uint8_t addr)
   165 {
   166 	uint8_t temp = 0;
   167 	m68k_end_timeslice();
   169 	switch (addr & 0x03) {
   170 		case WD2797_REG_STATUS:		// Status register
   171 			// Read from status register clears IRQ
   172 			ctx->irq = false;
   174 			// Get current status flags (set by last command)
   175 			// DRQ bit
   176 			if (ctx->cmd_has_drq) {
   177 				temp = ctx->status & ~0x03;
   178 				temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   179 				LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
   180 			} else {
   181 				temp = ctx->status & ~0x01;
   182 			}
   183 			// FDC is busy if there is still data in the buffer
   184 			temp |= (ctx->data_pos < ctx->data_len) ? 0x81 : 0x00;	// if data in buffer, then DMA hasn't copied it yet, and we're still busy!
   185 																	// TODO: also if seek delay / read delay hasn't passed (but that's for later)
   186 			return temp;
   188 		case WD2797_REG_TRACK:		// Track register
   189 			return ctx->track_reg;
   191 		case WD2797_REG_SECTOR:		// Sector register
   192 			return ctx->sector;
   194 		case WD2797_REG_DATA:		// Data register
   195 			// If there's data in the buffer, return it. Otherwise return 0xFF.
   196 			if (ctx->data_pos < ctx->data_len) {
   197 				// set IRQ if this is the last data byte
   198 				if (ctx->data_pos == (ctx->data_len-1)) {
   199 					// Set IRQ
   200 					ctx->irq = true;
   201 				}
   202 				// return data byte and increment pointer
   203 				return ctx->data[ctx->data_pos++];
   204 			} else {
   205 				// command finished
   206 				return ctx->data_reg;
   207 			}
   209 		default:
   210 			// shut up annoying compilers which don't recognise unreachable code when they see it
   211 			// (here's looking at you, gcc!)
   212 			return 0xff;
   213 	}
   214 }
   217 void wd2797_write_reg(WD2797_CTX *ctx, uint8_t addr, uint8_t val)
   218 {
   219 	uint8_t cmd = val & CMD_MASK;
   220 	size_t lba;
   221 	bool is_type1 = false;
   222 	int temp;
   223 	m68k_end_timeslice();
   225 	switch (addr) {
   226 		case WD2797_REG_COMMAND:	// Command register
   227 			// write to command register clears interrupt request
   228 			ctx->irq = false;
   230 			// Is the drive ready?
   231 			if (ctx->disc_image == NULL) {
   232 				// No disc image, thus the drive is busy.
   233 				ctx->status = 0x80;
   234 				return;
   235 			}
   237 			// Handle Type 1 commands
   238 			switch (cmd) {
   239 				case CMD_RESTORE:
   240 					// Restore. Set track to 0 and throw an IRQ.
   241 					is_type1 = true;
   242 					ctx->track = ctx->track_reg = 0;
   243 					break;
   245 				case CMD_SEEK:
   246 					// Seek. Seek to the track specced in the Data Register.
   247 					is_type1 = true;
   248 					if (ctx->data_reg < ctx->geom_tracks) {
   249 						ctx->track = ctx->track_reg = ctx->data_reg;
   250 					} else {
   251 						// Seek error. :(
   252 						ctx->status = 0x10;
   253 					}
   255 				case CMD_STEP:
   256 					// TODO! deal with trk0!
   257 					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   258 					is_type1 = true;
   259 					break;
   261 				case CMD_STEPIN:
   262 				case CMD_STEPOUT:
   263 				case CMD_STEP_TU:
   264 				case CMD_STEPIN_TU:
   265 				case CMD_STEPOUT_TU:
   266 					// if this is a Step In or Step Out cmd, set the step-direction
   267 					if ((cmd & ~0x10) == CMD_STEPIN) {
   268 						ctx->last_step_dir = 1;
   269 					} else if ((cmd & ~0x10) == CMD_STEPOUT) {
   270 						ctx->last_step_dir = -1;
   271 					}
   274 					// Seek one step in the last direction used.
   275 					ctx->track += ctx->last_step_dir;
   276 					if (ctx->track < 0) ctx->track = 0;
   277 					if (ctx->track >= ctx->geom_tracks) {
   278 						// Seek past end of disc... that'll be a Seek Error then.
   279 						ctx->status = 0x10;
   280 						ctx->track = ctx->geom_tracks - 1;
   281 					}
   282 					if (cmd & 0x10){
   283 						ctx->track_reg = ctx->track;
   284 					}
   286 					is_type1 = true;
   287 					break;
   289 				default:
   290 					break;
   291 			}
   293 			if (is_type1) {
   294 				// Terminate any sector reads or writes
   295 				ctx->data_len = ctx->data_pos = 0;
   297 				// No DRQ bit for these commands.
   298 				ctx->cmd_has_drq = false;
   300 				// Type1 status byte...
   301 				ctx->status = 0;
   302 				// S7 = Not Ready. Command executed, therefore the drive was ready... :)
   303 				// S6 = Write Protect. TODO: add this
   304 				// S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
   305 				ctx->status |= 0x20;
   306 				// S4 = Seek Error. Not bloody likely if we got down here...!
   307 				// S3 = CRC Error. Not gonna happen on a disc image!
   308 				// S2 = Track 0
   309 				ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
   310 				// S1 = Index Pulse. TODO -- need periodics to emulate this
   311 				// S0 = Busy. We just exec'd the command, thus we're not busy.
   312 				// 		TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
   314 				// Set IRQ
   315 				ctx->irq = true;
   316 				return;
   317 			}
   319 			// That's the Type 1 (seek) commands sorted. Now for the others.
   321 			// All these commands return the DRQ bit...
   322 			ctx->cmd_has_drq = true;
   324 			// If drive isn't ready, then set status B7 and exit
   325 			if (ctx->disc_image == NULL) {
   326 				ctx->status = 0x80;
   327 				return;
   328 			}
   330 			// If this is a Write command, check write protect status too
   331 			if (!ctx->writeable) {
   332 				// Write protected disc...
   333 				if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
   334 					// Set Write Protect bit and bail.
   335 					ctx->status = 0x40;
   337 					// Set IRQ
   338 					ctx->irq = true;
   340 					return;
   341 				}
   342 			}
   344 			// Disc is ready to go. Parse the command word.
   345 			switch (cmd) {
   346 				case CMD_READ_ADDRESS:
   347 					// Read Address
   348 					ctx->head = (val & 0x02) ? 1 : 0;
   350 					// reset data pointers
   351 					ctx->data_pos = ctx->data_len = 0;
   353 					// load data buffer
   354 					ctx->data[ctx->data_len++] = ctx->track;
   355 					ctx->data[ctx->data_len++] = ctx->head;
   356 					ctx->data[ctx->data_len++] = ctx->sector;
   357 					switch (ctx->geom_secsz) {
   358 						case 128:	ctx->data[ctx->data_len++] = 0; break;
   359 						case 256:	ctx->data[ctx->data_len++] = 1; break;
   360 						case 512:	ctx->data[ctx->data_len++] = 2; break;
   361 						case 1024:	ctx->data[ctx->data_len++] = 3; break;
   362 						default:	ctx->data[ctx->data_len++] = 0xFF; break;	// TODO: deal with invalid values better
   363 					}
   364 					ctx->data[ctx->data_len++] = 0;	// TODO: IDAM CRC!
   365 					ctx->data[ctx->data_len++] = 0;
   367 					ctx->status = 0;
   368 					// B6, B5 = 0
   369 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   370 					// B3 = CRC Error. Not possible.
   371 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   372 					// B1 = DRQ. Data request.
   373 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   374 					break;
   376 				case CMD_READ_SECTOR:
   377 				case CMD_READ_SECTOR_MULTI:
   378 					ctx->head = (val & 0x02) ? 1 : 0;
   379 					LOG("WD279X: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   380 					// Read Sector or Read Sector Multiple
   382 					// Check to see if the cyl, hd and sec are valid
   383 					if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt) || (ctx->sector == 0)) {
   384 						LOG("*** WD2797 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   385 								ctx->track, ctx->head, ctx->sector,
   386 								ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
   387 						// CHS parameters exceed limits
   388 						ctx->status = 0x10;		// Record Not Found
   389 						// Set IRQ
   390 						ctx->irq = true;
   391 						break;
   392 					}
   394 					// reset data pointers
   395 					ctx->data_pos = ctx->data_len = 0;
   397 					// Calculate number of sectors to read from disc
   398 					if (cmd == CMD_READ_SECTOR_MULTI)
   399 						temp = ctx->geom_spt;
   400 					else
   401 						temp = 1;
   403 					for (int i=0; i<temp; i++) {
   404 						// Calculate the LBA address of the required sector
   405 						// LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
   406 						lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i) - 1;
   407 						// convert LBA to byte address
   408 						lba *= ctx->geom_secsz;
   409 						LOG("\tREAD lba = %lu", lba);
   411 						// Read the sector from the file
   412 						fseek(ctx->disc_image, lba, SEEK_SET);
   413 						// TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   414 						ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
   415 						LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
   416 					}
   418 					ctx->status = 0;
   419 					// B6 = 0
   420 					// B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
   421 					// B4 = Record Not Found. Basically, the CHS parameters are bullcrap.
   422 					// B3 = CRC Error. Not possible.
   423 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   424 					// B1 = DRQ. Data request.
   425 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   426 					break;
   428 				case CMD_READ_TRACK:
   429 					// Read Track
   430 					// TODO! implement this
   431 					// ctx->head = (val & 0x02) ? 1 : 0;
   432 					// ctx->status = 0;
   433 					// B6, B5, B4, B3 = 0
   434 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   435 					// B1 = DRQ. Data request.
   436 					// ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   437 					ctx->irq = true;
   438 					ctx->status = 0x10;
   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 = 0;
   449 					// Calculate number of sectors to write to disc
   450 					if (cmd == CMD_WRITE_SECTOR_MULTI)
   451 						/*XXX: is this the correct value?*/
   452 						temp = ctx->geom_spt;
   453 					else
   454 						temp = 1;
   455 					ctx->data_len = temp * ctx->geom_secsz;
   457 					lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector)) - 1;
   458 					ctx->write_pos = lba * ctx->geom_secsz;
   460 					ctx->status = 0;
   461 					// B6 = Write Protect. This would have been set earlier.
   462 					// B5 = 0
   463 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   464 					// B3 = CRC Error. Not possible.
   465 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   466 					// B1 = DRQ. Data request.
   467 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   468 					break;
   470 				case CMD_FORMAT_TRACK:
   471 					// Write Track (aka Format Track)
   472 					ctx->head = (val & 0x02) ? 1 : 0;
   473 					ctx->status = 0;
   474 					// B6 = Write Protect. FIXME -- emulate this!
   475 					// B5, B4, B3 = 0
   476 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   477 					ctx->data_pos = 0;
   478 					ctx->data_len = 7170;
   479 					// B1 = DRQ. Data request.
   480 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   481 					ctx->formatting = true;
   482 					break;
   484 				case CMD_FORCE_INTERRUPT:
   485 					// Force Interrupt...
   486 					// Terminates current operation and sends an interrupt
   487 					// TODO!
   488 					ctx->status = 0x20;
   489 					if (!ctx->writeable){
   490 						ctx->status |= 0x40;
   491 					}
   492 					if (ctx->track == 0){
   493 						ctx->status = 0x04;
   494 					}
   495 					ctx->data_pos = ctx->data_len = 0;
   496 					if (cmd & 8){
   497 						// Set IRQ
   498 						ctx->irq = true;
   499 					}
   500 					break;
   501 			}
   502 			break;
   504 		case WD2797_REG_TRACK:		// Track register
   505 			ctx->track = ctx->track_reg = val;
   506 			break;
   508 		case WD2797_REG_SECTOR:		// Sector register
   509 			ctx->sector = val;
   510 			break;
   512 		case WD2797_REG_DATA:		// Data register
   513 			// Save the value written into the data register
   514 			ctx->data_reg = val;
   515 			// If we're processing a write command, and there's space in the
   516 			// buffer, allow the write.
   517 			if (ctx->data_pos < ctx->data_len && (ctx->write_pos >= 0 || ctx->formatting)) {
   518 				if (!ctx->formatting) ctx->data[ctx->data_pos] = val;
   519 				// store data byte and increment pointer
   520 				ctx->data_pos++;
   522 				// set IRQ and write data if this is the last data byte
   523 				if (ctx->data_pos == ctx->data_len) {
   524 					if (!ctx->formatting){
   525 						fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
   526 						fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
   527 						fflush(ctx->disc_image);
   528 					}
   529 					// Set IRQ and reset write pointer
   530 					ctx->irq = true;
   531 					ctx->write_pos = -1;
   532 					ctx->formatting = false;
   533 				}
   535 			}
   536 			break;
   537 	}
   538 }
   540 void wd2797_dma_miss(WD2797_CTX *ctx)
   541 {
   542 	ctx->data_pos = ctx->data_len;
   543 	ctx->write_pos = 0;
   544 	ctx->status = 4; /* lost data */
   545 	ctx->irq = true;
   546 }