src/wd279x.c

Sun, 12 Dec 2010 23:47:35 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Sun, 12 Dec 2010 23:47:35 +0000
changeset 55
ba6b8e570062
parent 54
57c6ef81ae81
child 57
feb84193a43a
child 76
2ef98ea1e944
permissions
-rw-r--r--

improve error and DMA handling

     1 #include <stdint.h>
     2 #include <stdbool.h>
     3 #include <malloc.h>
     4 #include "musashi/m68k.h"
     5 #include "wd279x.h"
     7 /// WD2797 command constants
     8 enum {
     9 	CMD_MASK				= 0xF0,		///< Bit mask to detect command bits
    10 	CMD_RESTORE				= 0x00,		///< Restore (recalibrate, seek to track 0)
    11 	CMD_SEEK				= 0x10,		///< Seek to given track
    12 	CMD_STEP				= 0x20,		///< Step
    13 	CMD_STEP_TU				= 0x30,		///< Step and update track register
    14 	CMD_STEPIN				= 0x40,		///< Step In
    15 	CMD_STEPIN_TU			= 0x50,		///< Step In and update track register
    16 	CMD_STEPOUT				= 0x60,		///< Step Out
    17 	CMD_STEPOUT_TU			= 0x70,		///< Step Out and update track register
    18 	CMD_READ_SECTOR			= 0x80,		///< Read Sector
    19 	CMD_READ_SECTOR_MULTI	= 0x90,		///< Read Multiple Sectors
    20 	CMD_WRITE_SECTOR		= 0xA0,		///< Write Sector
    21 	CMD_WRITE_SECTOR_MULTI	= 0xB0,		///< Write Multiple Sectors
    22 	CMD_READ_ADDRESS		= 0xC0,		///< Read Address (IDAM contents)
    23 	CMD_FORCE_INTERRUPT		= 0xD0,		///< Force Interrupt
    24 	CMD_READ_TRACK			= 0xE0,		///< Read Track
    25 	CMD_FORMAT_TRACK		= 0xF0		///< Format Track
    26 };
    29 void wd2797_init(WD2797_CTX *ctx)
    30 {
    31 	// track, head and sector unknown
    32 	ctx->track = ctx->head = ctx->sector = 0;
    34 	// no IRQ pending
    35 	ctx->irql = ctx->irqe = false;
    37 	// no data available
    38 	ctx->data_pos = ctx->data_len = 0;
    39 	ctx->data = NULL;
    41 	// Status register clear, not busy
    42 	ctx->status = 0;
    44 	// Clear data register
    45 	ctx->data_reg = 0;
    47 	// Last step direction
    48 	ctx->last_step_dir = -1;
    50 	// No disc image loaded
    51 	ctx->disc_image = NULL;
    52 	ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = ctx->geom_tracks = 0;
    53 }
    56 void wd2797_reset(WD2797_CTX *ctx)
    57 {
    58 	// track, head and sector unknown
    59 	ctx->track = ctx->head = ctx->sector = 0;
    61 	// no IRQ pending
    62 	ctx->irql = ctx->irqe = false;
    64 	// no data available
    65 	ctx->data_pos = ctx->data_len = 0;
    67 	// Status register clear, not busy
    68 	ctx->status = 0;
    70 	// Clear data register
    71 	ctx->data_reg = 0;
    73 	// Last step direction
    74 	ctx->last_step_dir = -1;
    75 }
    78 void wd2797_done(WD2797_CTX *ctx)
    79 {
    80 	// Reset the WD2797
    81 	wd2797_reset(ctx);
    83 	// Free any allocated memory
    84 	if (ctx->data) {
    85 		free(ctx->data);
    86 		ctx->data = NULL;
    87 	}
    88 }
    91 bool wd2797_get_irq(WD2797_CTX *ctx)
    92 {
    93 	// If an IRQ is pending, clear it and return true, otherwise return false
    94 	if (ctx->irqe) {
    95 		ctx->irqe = false;
    96 		return true;
    97 	} else {
    98 		return false;
    99 	}
   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->irql = false;
   168 			ctx->irqe = false;
   170 			// Get current status flags (set by last command)
   171 			// DRQ bit
   172 			if (ctx->cmd_has_drq) {
   173 				printf("\tWDFDC rd sr, has drq, pos=%lu len=%lu\n", ctx->data_pos, ctx->data_len);
   174 				temp = ctx->status & ~0x03;
   175 				temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   176 			} else {
   177 				temp = ctx->status & ~0x01;
   178 			}
   179 			// FDC is busy if there is still data in the buffer
   180 			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!
   181 																	// TODO: also if seek delay / read delay hasn't passed (but that's for later)
   182 			return temp;
   184 		case WD2797_REG_TRACK:		// Track register
   185 			return ctx->track;
   187 		case WD2797_REG_SECTOR:		// Sector register
   188 			return ctx->sector;
   190 		case WD2797_REG_DATA:		// Data register
   191 			// If there's data in the buffer, return it. Otherwise return 0xFF.
   192 			if (ctx->data_pos < ctx->data_len) {
   193 				// set IRQ if this is the last data byte
   194 				if (ctx->data_pos == (ctx->data_len-1)) {
   195 					// Set IRQ only if IRQL has been cleared (no pending IRQs)
   196 					ctx->irqe = ctx->irql ? ctx->irqe : true;
   197 					ctx->irql = true;
   198 				}
   199 				// return data byte and increment pointer
   200 				return ctx->data[ctx->data_pos++];
   201 			} else {
   202 				// command finished
   203 				return 0xff;
   204 			}
   206 		default:
   207 			// shut up annoying compilers which don't recognise unreachable code when they see it
   208 			// (here's looking at you, gcc!)
   209 			return 0xff;
   210 	}
   211 }
   214 void wd2797_write_reg(WD2797_CTX *ctx, uint8_t addr, uint8_t val)
   215 {
   216 	uint8_t cmd = val & CMD_MASK;
   217 	size_t lba;
   218 	bool is_type1 = false;
   219 	int temp;
   221 	m68k_end_timeslice();
   223 	switch (addr) {
   224 		case WD2797_REG_COMMAND:	// Command register
   225 			// write to command register clears interrupt request
   226 			ctx->irql = false;
   228 			// Is the drive ready?
   229 			if (ctx->disc_image == NULL) {
   230 				// No disc image, thus the drive is busy.
   231 				ctx->status = 0x80;
   232 				return;
   233 			}
   235 			// Handle Type 1 commands
   236 			switch (cmd) {
   237 				case CMD_RESTORE:
   238 					// Restore. Set track to 0 and throw an IRQ.
   239 					is_type1 = true;
   240 					ctx->track = 0;
   241 					break;
   243 				case CMD_SEEK:
   244 					// Seek. Seek to the track specced in the Data Register.
   245 					is_type1 = true;
   246 					if (ctx->data_reg < ctx->geom_tracks) {
   247 						ctx->track = ctx->data_reg;
   248 					} else {
   249 						// Seek error. :(
   250 						ctx->status = 0x10;
   251 					}
   253 				case CMD_STEP:
   254 					// TODO! deal with trk0!
   255 					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   256 					is_type1 = true;
   257 					break;
   259 				case CMD_STEPIN:
   260 				case CMD_STEPOUT:
   261 					// TODO! deal with trk0!
   262 					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   263 					if (cmd == CMD_STEPIN) {
   264 						ctx->last_step_dir = 1;
   265 					} else {
   266 						ctx->last_step_dir = -1;
   267 					}
   268 					is_type1 = true;
   269 					break;
   271 				case CMD_STEP_TU:
   272 				case CMD_STEPIN_TU:
   273 				case CMD_STEPOUT_TU:
   274 					// if this is a Step In or Step Out cmd, set the step-direction
   275 					if (cmd == CMD_STEPIN_TU) {
   276 						ctx->last_step_dir = 1;
   277 					} else if (cmd == CMD_STEPOUT_TU) {
   278 						ctx->last_step_dir = -1;
   279 					}
   281 					// Seek one step in the last direction used.
   282 					ctx->track += ctx->last_step_dir;
   283 					if (ctx->track < 0) ctx->track = 0;
   284 					if (ctx->track >= ctx->geom_tracks) {
   285 						// Seek past end of disc... that'll be a Seek Error then.
   286 						ctx->status = 0x10;
   287 						ctx->track = ctx->geom_tracks - 1;
   288 					}
   289 					is_type1 = true;
   290 					break;
   292 				default:
   293 					break;
   294 			}
   296 			if (is_type1) {
   297 				// Terminate any sector reads or writes
   298 				ctx->data_len = ctx->data_pos = 0;
   300 				// No DRQ bit for these commands.
   301 				ctx->cmd_has_drq = false;
   303 				// Type1 status byte...
   304 				ctx->status = 0;
   305 				// S7 = Not Ready. Command executed, therefore the drive was ready... :)
   306 				// S6 = Write Protect. TODO: add this
   307 				// S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
   308 				ctx->status |= 0x20;
   309 				// S4 = Seek Error. Not bloody likely if we got down here...!
   310 				// S3 = CRC Error. Not gonna happen on a disc image!
   311 				// S2 = Track 0
   312 				ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
   313 				// S1 = Index Pulse. TODO -- need periodics to emulate this
   314 				// S0 = Busy. We just exec'd the command, thus we're not busy.
   315 				// 		TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
   317 				// Set IRQ only if IRQL has been cleared (no pending IRQs)
   318 				ctx->irqe = ctx->irql ? ctx->irqe : true;
   319 				ctx->irql = true;
   320 				return;
   321 			}
   323 			// That's the Type 1 (seek) commands sorted. Now for the others.
   325 			// All these commands return the DRQ bit...
   326 			ctx->cmd_has_drq = true;
   328 			// If drive isn't ready, then set status B7 and exit
   329 			if (ctx->disc_image == NULL) {
   330 				ctx->status = 0x80;
   331 				return;
   332 			}
   334 			// If this is a Write command, check write protect status too
   335 			// TODO!
   336 			if (false) {
   337 				// Write protected disc...
   338 				if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
   339 					// Set Write Protect bit and bail.
   340 					ctx->status = 0x40;
   342 					// Set IRQ only if IRQL has been cleared (no pending IRQs)
   343 					ctx->irqe = ctx->irql ? ctx->irqe : true;
   344 					ctx->irql = true;
   346 					return;
   347 				}
   348 			}
   350 			// Disc is ready to go. Parse the command word.
   351 			switch (cmd) {
   352 				case CMD_READ_ADDRESS:
   353 					// Read Address
   354 					ctx->head = (val & 0x02) ? 1 : 0;
   356 					// reset data pointers
   357 					ctx->data_pos = ctx->data_len = 0;
   359 					// load data buffer
   360 					ctx->data[ctx->data_len++] = ctx->track;
   361 					ctx->data[ctx->data_len++] = ctx->head;
   362 					ctx->data[ctx->data_len++] = ctx->sector;
   363 					switch (ctx->geom_secsz) {
   364 						case 128:	ctx->data[ctx->data_len++] = 0; break;
   365 						case 256:	ctx->data[ctx->data_len++] = 1; break;
   366 						case 512:	ctx->data[ctx->data_len++] = 2; break;
   367 						case 1024:	ctx->data[ctx->data_len++] = 3; break;
   368 						default:	ctx->data[ctx->data_len++] = 0xFF; break;	// TODO: deal with invalid values better
   369 					}
   370 					ctx->data[ctx->data_len++] = 0;	// TODO: IDAM CRC!
   371 					ctx->data[ctx->data_len++] = 0;
   373 					ctx->status = 0;
   374 					// B6, B5 = 0
   375 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   376 					// B3 = CRC Error. Not possible.
   377 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   378 					// B1 = DRQ. Data request.
   379 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   380 					break;
   382 				case CMD_READ_SECTOR:
   383 				case CMD_READ_SECTOR_MULTI:
   384 					ctx->head = (val & 0x02) ? 1 : 0;
   385 					printf("WD279X: READ SECTOR cmd=%02X chs=%d:%d:%d\n", cmd, ctx->track, ctx->head, ctx->sector);
   386 					// Read Sector or Read Sector Multiple
   387 					// reset data pointers
   388 					ctx->data_pos = ctx->data_len = 0;
   390 					// Calculate number of sectors to read from disc
   391 					if (cmd == CMD_READ_SECTOR_MULTI)
   392 						temp = ctx->geom_spt;
   393 					else
   394 						temp = 1;
   396 					for (int i=0; i<temp; i++) {
   397 						// Calculate the LBA address of the required sector
   398 						lba = ((((ctx->track * ctx->geom_heads) + ctx->head) * ctx->geom_spt) + ((ctx->sector + i - 1) % ctx->geom_spt)) * ctx->geom_secsz;
   399 						printf("\tREAD lba = %lu\n", lba);
   401 						// Read the sector from the file
   402 						fseek(ctx->disc_image, lba, SEEK_SET);
   403 						ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
   404 						printf("\tREAD len=%lu, pos=%lu, ssz=%d\n", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
   405 						// TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   406 					}
   408 					ctx->status = 0;
   409 					// B6 = 0
   410 					// B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
   411 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   412 					// B3 = CRC Error. Not possible.
   413 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   414 					// B1 = DRQ. Data request.
   415 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   416 					break;
   418 				case CMD_READ_TRACK:
   419 					// Read Track
   420 					// TODO! implement this
   421 					ctx->head = (val & 0x02) ? 1 : 0;
   422 					ctx->status = 0;
   423 					// B6, B5, B4, B3 = 0
   424 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   425 					// B1 = DRQ. Data request.
   426 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   427 					break;
   429 				case CMD_WRITE_SECTOR:
   430 				case CMD_WRITE_SECTOR_MULTI:
   431 					// Write Sector or Write Sector Multiple
   433 					ctx->head = (val & 0x02) ? 1 : 0;
   434 					// reset data pointers
   435 					ctx->data_pos = ctx->data_len = 0;
   437 					// TODO: set "write pending" flag, and write LBA, and go from there.
   439 					ctx->status = 0;
   440 					// B6 = Write Protect. FIXME -- emulate this!
   441 					// B5 = 0
   442 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   443 					// B3 = CRC Error. Not possible.
   444 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   445 					// B1 = DRQ. Data request.
   446 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   447 					break;
   449 				case CMD_FORMAT_TRACK:
   450 					// Write Track (aka Format Track)
   451 					ctx->head = (val & 0x02) ? 1 : 0;
   452 					ctx->status = 0;
   453 					// B6 = Write Protect. FIXME -- emulate this!
   454 					// B5, B4, B3 = 0
   455 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   456 					// B1 = DRQ. Data request.
   457 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   458 					break;
   460 				case CMD_FORCE_INTERRUPT:
   461 					// Force Interrupt...
   462 					// Terminates current operation and sends an interrupt
   463 					// TODO!
   464 					ctx->status = 0;
   465 					ctx->data_pos = ctx->data_len = 0;
   466 					// Set IRQ only if IRQL has been cleared (no pending IRQs)
   467 					ctx->irqe = ctx->irql ? ctx->irqe : true;
   468 					ctx->irql = true;
   469 					break;
   470 			}
   471 			break;
   473 		case WD2797_REG_TRACK:		// Track register
   474 			ctx->track = val;
   475 			break;
   477 		case WD2797_REG_SECTOR:		// Sector register
   478 			ctx->sector = val;
   479 			break;
   481 		case WD2797_REG_DATA:		// Data register
   482 			// Save the value written into the data register
   483 			ctx->data_reg = val;
   485 			// If we're processing a write command, and there's space in the
   486 			// buffer, allow the write.
   487 			if (ctx->data_pos < ctx->data_len) {
   488 				// set IRQ if this is the last data byte
   489 				if (ctx->data_pos == (ctx->data_len-1)) {
   490 					// Set IRQ only if IRQL has been cleared (no pending IRQs)
   491 					ctx->irqe = ctx->irql ? ctx->irqe : true;
   492 					ctx->irql = true;
   493 				}
   495 				// store data byte and increment pointer
   496 				ctx->data[ctx->data_pos++] = val;
   497 			}
   498 			break;
   499 	}
   500 }