src/wd279x.c

Mon, 06 Dec 2010 08:27:21 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Mon, 06 Dec 2010 08:27:21 +0000
changeset 76
2ef98ea1e944
parent 54
57c6ef81ae81
child 77
e7898cbae0c6
child 78
c149c13aff1c
permissions
-rw-r--r--

move edge-sensitive FDC IRQ to main()

     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->irq = false;
    37 	// no data available
    38 	ctx->data_pos = ctx->data_len = 0;
    39 	ctx->data = NULL;
    41 	// Status register clear, not busy; type1 command
    42 	ctx->status = 0;
    43 	ctx->cmd_has_drq = false;
    45 	// Clear data register
    46 	ctx->data_reg = 0;
    48 	// Last step direction = "towards zero"
    49 	ctx->last_step_dir = -1;
    51 	// No disc image loaded
    52 	ctx->disc_image = NULL;
    53 	ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = ctx->geom_tracks = 0;
    54 }
    57 void wd2797_reset(WD2797_CTX *ctx)
    58 {
    59 	// track, head and sector unknown
    60 	ctx->track = ctx->head = ctx->sector = 0;
    62 	// no IRQ pending
    63 	ctx->irq = false;
    65 	// no data available
    66 	ctx->data_pos = ctx->data_len = 0;
    68 	// Status register clear, not busy
    69 	ctx->status = 0;
    71 	// Clear data register
    72 	ctx->data_reg = 0;
    74 	// Last step direction
    75 	ctx->last_step_dir = -1;
    76 }
    79 void wd2797_done(WD2797_CTX *ctx)
    80 {
    81 	// Reset the WD2797
    82 	wd2797_reset(ctx);
    84 	// Free any allocated memory
    85 	if (ctx->data) {
    86 		free(ctx->data);
    87 		ctx->data = NULL;
    88 	}
    89 }
    92 bool wd2797_get_irq(WD2797_CTX *ctx)
    93 {
    94 	return ctx->irq;
    95 }
    98 bool wd2797_get_drq(WD2797_CTX *ctx)
    99 {
   100 	return (ctx->data_pos < ctx->data_len);
   101 }
   104 WD2797_ERR wd2797_load(WD2797_CTX *ctx, FILE *fp, int secsz, int spt, int heads)
   105 {
   106 	size_t filesize;
   108 	// Start by finding out how big the image file is
   109 	fseek(fp, 0, SEEK_END);
   110 	filesize = ftell(fp);
   111 	fseek(fp, 0, SEEK_SET);
   113 	// Now figure out how many tracks it contains
   114 	int tracks = filesize / secsz / spt / heads;
   115 	// Confirm...
   116 	if (tracks < 1) {
   117 		return WD2797_ERR_BAD_GEOM;
   118 	}
   120 	// Allocate enough memory to store one disc track
   121 	if (ctx->data) {
   122 		free(ctx->data);
   123 	}
   124 	ctx->data = malloc(secsz * spt);
   125 	if (!ctx->data)
   126 		return WD2797_ERR_NO_MEMORY;
   128 	// Load the image and the geometry data
   129 	ctx->disc_image = fp;
   130 	ctx->geom_tracks = tracks;
   131 	ctx->geom_secsz = secsz;
   132 	ctx->geom_heads = heads;
   133 	ctx->geom_spt = spt;
   135 	return WD2797_ERR_OK;
   136 }
   139 void wd2797_unload(WD2797_CTX *ctx)
   140 {
   141 	// Free memory buffer
   142 	if (ctx->data) {
   143 		free(ctx->data);
   144 		ctx->data = NULL;
   145 	}
   147 	// Clear file pointer
   148 	ctx->disc_image = NULL;
   150 	// Clear the disc geometry
   151 	ctx->geom_tracks = ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = 0;
   152 }
   155 uint8_t wd2797_read_reg(WD2797_CTX *ctx, uint8_t addr)
   156 {
   157 	uint8_t temp = 0;
   159 	switch (addr & 0x03) {
   160 		case WD2797_REG_STATUS:		// Status register
   161 			// Read from status register clears IRQ
   162 			ctx->irq = false;
   164 			// Get current status flags (set by last command)
   165 			// DRQ bit
   166 			if (ctx->cmd_has_drq) {
   167 				printf("\tWDFDC rd sr, has drq, pos=%lu len=%lu\n", ctx->data_pos, ctx->data_len);
   168 				temp = ctx->status & ~0x03;
   169 				temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   170 			} else {
   171 				temp = ctx->status & ~0x01;
   172 			}
   173 			// FDC is busy if there is still data in the buffer
   174 			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!
   175 																	// TODO: also if seek delay / read delay hasn't passed (but that's for later)
   176 			return temp;
   178 		case WD2797_REG_TRACK:		// Track register
   179 			return ctx->track;
   181 		case WD2797_REG_SECTOR:		// Sector register
   182 			return ctx->sector;
   184 		case WD2797_REG_DATA:		// Data register
   185 			// If there's data in the buffer, return it. Otherwise return 0xFF.
   186 			if (ctx->data_pos < ctx->data_len) {
   187 				// set IRQ if this is the last data byte
   188 				if (ctx->data_pos == (ctx->data_len-1)) {
   189 					// Set IRQ
   190 					ctx->irq = true;
   191 				}
   192 				// return data byte and increment pointer
   193 				return ctx->data[ctx->data_pos++];
   194 			} else {
   195 				// command finished
   196 				return 0xff;
   197 			}
   199 		default:
   200 			// shut up annoying compilers which don't recognise unreachable code when they see it
   201 			// (here's looking at you, gcc!)
   202 			return 0xff;
   203 	}
   204 }
   207 void wd2797_write_reg(WD2797_CTX *ctx, uint8_t addr, uint8_t val)
   208 {
   209 	uint8_t cmd = val & CMD_MASK;
   210 	size_t lba;
   211 	bool is_type1 = false;
   212 	int temp;
   214 	m68k_end_timeslice();
   216 	switch (addr) {
   217 		case WD2797_REG_COMMAND:	// Command register
   218 			// write to command register clears interrupt request
   219 			ctx->irq = false;
   221 			// Is the drive ready?
   222 			if (ctx->disc_image == NULL) {
   223 				// No disc image, thus the drive is busy.
   224 				ctx->status = 0x80;
   225 				return;
   226 			}
   228 			// Handle Type 1 commands
   229 			switch (cmd) {
   230 				case CMD_RESTORE:
   231 					// Restore. Set track to 0 and throw an IRQ.
   232 					is_type1 = true;
   233 					ctx->track = 0;
   234 					break;
   236 				case CMD_SEEK:
   237 					// Seek. Seek to the track specced in the Data Register.
   238 					is_type1 = true;
   239 					if (ctx->data_reg < ctx->geom_tracks) {
   240 						ctx->track = ctx->data_reg;
   241 					} else {
   242 						// Seek error. :(
   243 						ctx->status = 0x10;
   244 					}
   246 				case CMD_STEP:
   247 					// TODO! deal with trk0!
   248 					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   249 					is_type1 = true;
   250 					break;
   252 				case CMD_STEPIN:
   253 				case CMD_STEPOUT:
   254 					// TODO! deal with trk0!
   255 					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   256 					if (cmd == CMD_STEPIN) {
   257 						ctx->last_step_dir = 1;
   258 					} else {
   259 						ctx->last_step_dir = -1;
   260 					}
   261 					is_type1 = true;
   262 					break;
   264 				case CMD_STEP_TU:
   265 				case CMD_STEPIN_TU:
   266 				case CMD_STEPOUT_TU:
   267 					// if this is a Step In or Step Out cmd, set the step-direction
   268 					if (cmd == CMD_STEPIN_TU) {
   269 						ctx->last_step_dir = 1;
   270 					} else if (cmd == CMD_STEPOUT_TU) {
   271 						ctx->last_step_dir = -1;
   272 					}
   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 					is_type1 = true;
   283 					break;
   285 				default:
   286 					break;
   287 			}
   289 			if (is_type1) {
   290 				// Terminate any sector reads or writes
   291 				ctx->data_len = ctx->data_pos = 0;
   293 				// No DRQ bit for these commands.
   294 				ctx->cmd_has_drq = false;
   296 				// Type1 status byte...
   297 				ctx->status = 0;
   298 				// S7 = Not Ready. Command executed, therefore the drive was ready... :)
   299 				// S6 = Write Protect. TODO: add this
   300 				// S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
   301 				ctx->status |= 0x20;
   302 				// S4 = Seek Error. Not bloody likely if we got down here...!
   303 				// S3 = CRC Error. Not gonna happen on a disc image!
   304 				// S2 = Track 0
   305 				ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
   306 				// S1 = Index Pulse. TODO -- need periodics to emulate this
   307 				// S0 = Busy. We just exec'd the command, thus we're not busy.
   308 				// 		TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
   310 				// Set IRQ
   311 				ctx->irq = true;
   312 				return;
   313 			}
   315 			// That's the Type 1 (seek) commands sorted. Now for the others.
   317 			// All these commands return the DRQ bit...
   318 			ctx->cmd_has_drq = true;
   320 			// If drive isn't ready, then set status B7 and exit
   321 			if (ctx->disc_image == NULL) {
   322 				ctx->status = 0x80;
   323 				return;
   324 			}
   326 			// If this is a Write command, check write protect status too
   327 			// TODO!
   328 			if (false) {
   329 				// Write protected disc...
   330 				if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
   331 					// Set Write Protect bit and bail.
   332 					ctx->status = 0x40;
   334 					// Set IRQ
   335 					ctx->irq = true;
   337 					return;
   338 				}
   339 			}
   341 			// Disc is ready to go. Parse the command word.
   342 			switch (cmd) {
   343 				case CMD_READ_ADDRESS:
   344 					// Read Address
   345 					ctx->head = (val & 0x02) ? 1 : 0;
   347 					// reset data pointers
   348 					ctx->data_pos = ctx->data_len = 0;
   350 					// load data buffer
   351 					ctx->data[ctx->data_len++] = ctx->track;
   352 					ctx->data[ctx->data_len++] = ctx->head;
   353 					ctx->data[ctx->data_len++] = ctx->sector;
   354 					switch (ctx->geom_secsz) {
   355 						case 128:	ctx->data[ctx->data_len++] = 0; break;
   356 						case 256:	ctx->data[ctx->data_len++] = 1; break;
   357 						case 512:	ctx->data[ctx->data_len++] = 2; break;
   358 						case 1024:	ctx->data[ctx->data_len++] = 3; break;
   359 						default:	ctx->data[ctx->data_len++] = 0xFF; break;	// TODO: deal with invalid values better
   360 					}
   361 					ctx->data[ctx->data_len++] = 0;	// TODO: IDAM CRC!
   362 					ctx->data[ctx->data_len++] = 0;
   364 					ctx->status = 0;
   365 					// B6, B5 = 0
   366 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   367 					// B3 = CRC Error. Not possible.
   368 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   369 					// B1 = DRQ. Data request.
   370 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   371 					break;
   373 				case CMD_READ_SECTOR:
   374 				case CMD_READ_SECTOR_MULTI:
   375 					ctx->head = (val & 0x02) ? 1 : 0;
   376 					printf("WD279X: READ SECTOR cmd=%02X chs=%d:%d:%d\n", cmd, ctx->track, ctx->head, ctx->sector);
   377 					// Read Sector or Read Sector Multiple
   378 					// reset data pointers
   379 					ctx->data_pos = ctx->data_len = 0;
   381 					// Calculate number of sectors to read from disc
   382 					if (cmd == CMD_READ_SECTOR_MULTI)
   383 						temp = ctx->geom_spt;
   384 					else
   385 						temp = 1;
   387 					for (int i=0; i<temp; i++) {
   388 						// Calculate the LBA address of the required sector
   389 						lba = ((((ctx->track * ctx->geom_heads) + ctx->head) * ctx->geom_spt) + ((ctx->sector + i - 1) % ctx->geom_spt)) * ctx->geom_secsz;
   390 						printf("\tREAD lba = %lu\n", lba);
   392 						// Read the sector from the file
   393 						fseek(ctx->disc_image, lba, SEEK_SET);
   394 						ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
   395 						printf("\tREAD len=%lu, pos=%lu, ssz=%d\n", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
   396 						// TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   397 					}
   399 					ctx->status = 0;
   400 					// B6 = 0
   401 					// B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
   402 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   403 					// B3 = CRC Error. Not possible.
   404 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   405 					// B1 = DRQ. Data request.
   406 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   407 					break;
   409 				case CMD_READ_TRACK:
   410 					// Read Track
   411 					// TODO! implement this
   412 					ctx->head = (val & 0x02) ? 1 : 0;
   413 					ctx->status = 0;
   414 					// B6, B5, B4, B3 = 0
   415 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   416 					// B1 = DRQ. Data request.
   417 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   418 					break;
   420 				case CMD_WRITE_SECTOR:
   421 				case CMD_WRITE_SECTOR_MULTI:
   422 					// Write Sector or Write Sector Multiple
   424 					ctx->head = (val & 0x02) ? 1 : 0;
   425 					// reset data pointers
   426 					ctx->data_pos = ctx->data_len = 0;
   428 					// TODO: set "write pending" flag, and write LBA, and go from there.
   430 					ctx->status = 0;
   431 					// B6 = Write Protect. FIXME -- emulate this!
   432 					// B5 = 0
   433 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   434 					// B3 = CRC Error. Not possible.
   435 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   436 					// B1 = DRQ. Data request.
   437 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   438 					break;
   440 				case CMD_FORMAT_TRACK:
   441 					// Write Track (aka Format Track)
   442 					ctx->head = (val & 0x02) ? 1 : 0;
   443 					ctx->status = 0;
   444 					// B6 = Write Protect. FIXME -- emulate this!
   445 					// B5, B4, B3 = 0
   446 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   447 					// B1 = DRQ. Data request.
   448 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   449 					break;
   451 				case CMD_FORCE_INTERRUPT:
   452 					// Force Interrupt...
   453 					// Terminates current operation and sends an interrupt
   454 					// TODO!
   455 					ctx->status = 0;
   456 					ctx->data_pos = ctx->data_len = 0;
   457 					// Set IRQ
   458 					ctx->irq = true;
   459 					break;
   460 			}
   461 			break;
   463 		case WD2797_REG_TRACK:		// Track register
   464 			ctx->track = val;
   465 			break;
   467 		case WD2797_REG_SECTOR:		// Sector register
   468 			ctx->sector = val;
   469 			break;
   471 		case WD2797_REG_DATA:		// Data register
   472 			// Save the value written into the data register
   473 			ctx->data_reg = val;
   475 			// If we're processing a write command, and there's space in the
   476 			// buffer, allow the write.
   477 			if (ctx->data_pos < ctx->data_len) {
   478 				// set IRQ if this is the last data byte
   479 				if (ctx->data_pos == (ctx->data_len-1)) {
   480 					// Set IRQ
   481 					ctx->irq = true;
   482 				}
   484 				// store data byte and increment pointer
   485 				ctx->data[ctx->data_pos++] = val;
   486 			}
   487 			break;
   488 	}
   489 }