src/wd279x.c

Mon, 06 Dec 2010 01:43:04 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Mon, 06 Dec 2010 01:43:04 +0000
changeset 54
57c6ef81ae81
parent 53
e1693c4b8a0c
child 57
feb84193a43a
child 76
2ef98ea1e944
permissions
-rw-r--r--

fix side-select bug in WDC FDC driver, was causing all reads to occur on side0... now the Loader boots!

Loader will boot, but immediately gives up on the floppy drive... Not sure why.

     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 }