src/wd279x.c

Sun, 05 Dec 2010 16:18:50 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Sun, 05 Dec 2010 16:18:50 +0000
changeset 51
e5a92d7beecf
parent 49
545798274dad
child 53
e1693c4b8a0c
permissions
-rw-r--r--

fix CHS => LBA calculation for multisector reads

Wrapping was not taken into account...

     1 #include <stdint.h>
     2 #include <stdbool.h>
     3 #include <malloc.h>
     4 #include "wd279x.h"
     6 /// WD2797 command constants
     7 enum {
     8 	CMD_MASK				= 0xF0,		///< Bit mask to detect command bits
     9 	CMD_RESTORE				= 0x00,		///< Restore (recalibrate, seek to track 0)
    10 	CMD_SEEK				= 0x10,		///< Seek to given track
    11 	CMD_STEP				= 0x20,		///< Step
    12 	CMD_STEP_TU				= 0x30,		///< Step and update track register
    13 	CMD_STEPIN				= 0x40,		///< Step In
    14 	CMD_STEPIN_TU			= 0x50,		///< Step In and update track register
    15 	CMD_STEPOUT				= 0x60,		///< Step Out
    16 	CMD_STEPOUT_TU			= 0x70,		///< Step Out and update track register
    17 	CMD_READ_SECTOR			= 0x80,		///< Read Sector
    18 	CMD_READ_SECTOR_MULTI	= 0x90,		///< Read Multiple Sectors
    19 	CMD_WRITE_SECTOR		= 0xA0,		///< Write Sector
    20 	CMD_WRITE_SECTOR_MULTI	= 0xB0,		///< Write Multiple Sectors
    21 	CMD_READ_ADDRESS		= 0xC0,		///< Read Address (IDAM contents)
    22 	CMD_FORCE_INTERRUPT		= 0xD0,		///< Force Interrupt
    23 	CMD_READ_TRACK			= 0xE0,		///< Read Track
    24 	CMD_FORMAT_TRACK		= 0xF0		///< Format Track
    25 };
    28 void wd2797_init(WD2797_CTX *ctx)
    29 {
    30 	// track, head and sector unknown
    31 	ctx->track = ctx->head = ctx->sector = 0;
    33 	// no IRQ pending
    34 	ctx->irql = ctx->irqe = false;
    36 	// no data available
    37 	ctx->data_pos = ctx->data_len = 0;
    38 	ctx->data = NULL;
    40 	// Status register clear, not busy
    41 	ctx->status = 0;
    43 	// Clear data register
    44 	ctx->data_reg = 0;
    46 	// Last step direction
    47 	ctx->last_step_dir = -1;
    49 	// No disc image loaded
    50 	ctx->disc_image = NULL;
    51 	ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = ctx->geom_tracks = 0;
    52 }
    55 void wd2797_reset(WD2797_CTX *ctx)
    56 {
    57 	// track, head and sector unknown
    58 	ctx->track = ctx->head = ctx->sector = 0;
    60 	// no IRQ pending
    61 	ctx->irql = ctx->irqe = false;
    63 	// no data available
    64 	ctx->data_pos = ctx->data_len = 0;
    66 	// Status register clear, not busy
    67 	ctx->status = 0;
    69 	// Clear data register
    70 	ctx->data_reg = 0;
    72 	// Last step direction
    73 	ctx->last_step_dir = -1;
    74 }
    77 void wd2797_done(WD2797_CTX *ctx)
    78 {
    79 	// Reset the WD2797
    80 	wd2797_reset(ctx);
    82 	// Free any allocated memory
    83 	if (ctx->data) {
    84 		free(ctx->data);
    85 		ctx->data = NULL;
    86 	}
    87 }
    90 bool wd2797_get_irq(WD2797_CTX *ctx)
    91 {
    92 	// If an IRQ is pending, clear it and return true, otherwise return false
    93 	if (ctx->irqe) {
    94 		ctx->irqe = false;
    95 		return true;
    96 	} else {
    97 		return false;
    98 	}
    99 }
   102 bool wd2797_get_drq(WD2797_CTX *ctx)
   103 {
   104 	return (ctx->data_pos < ctx->data_len);
   105 }
   108 WD2797_ERR wd2797_load(WD2797_CTX *ctx, FILE *fp, int secsz, int spt, int heads)
   109 {
   110 	size_t filesize;
   112 	// Start by finding out how big the image file is
   113 	fseek(fp, 0, SEEK_END);
   114 	filesize = ftell(fp);
   115 	fseek(fp, 0, SEEK_SET);
   117 	// Now figure out how many tracks it contains
   118 	int tracks = filesize / secsz / spt / heads;
   119 	// Confirm...
   120 	if (tracks < 1) {
   121 		return WD2797_ERR_BAD_GEOM;
   122 	}
   124 	// Allocate enough memory to store one disc track
   125 	if (ctx->data) {
   126 		free(ctx->data);
   127 	}
   128 	ctx->data = malloc(secsz * spt);
   129 	if (!ctx->data)
   130 		return WD2797_ERR_NO_MEMORY;
   132 	// Load the image and the geometry data
   133 	ctx->disc_image = fp;
   134 	ctx->geom_tracks = tracks;
   135 	ctx->geom_secsz = secsz;
   136 	ctx->geom_heads = heads;
   137 	ctx->geom_spt = spt;
   139 	return WD2797_ERR_OK;
   140 }
   143 void wd2797_unload(WD2797_CTX *ctx)
   144 {
   145 	// Free memory buffer
   146 	if (ctx->data) {
   147 		free(ctx->data);
   148 		ctx->data = NULL;
   149 	}
   151 	// Clear file pointer
   152 	ctx->disc_image = NULL;
   154 	// Clear the disc geometry
   155 	ctx->geom_tracks = ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = 0;
   156 }
   159 uint8_t wd2797_read_reg(WD2797_CTX *ctx, uint8_t addr)
   160 {
   161 	uint8_t temp = 0;
   163 	switch (addr & 0x03) {
   164 		case WD2797_REG_STATUS:		// Status register
   165 			// Read from status register clears IRQ
   166 			ctx->irql = false;
   168 			// Get current status flags (set by last command)
   169 			temp = ctx->status;
   170 			// DRQ bit
   171 			if (ctx->cmd_has_drq)
   172 				temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   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 				// return data byte and increment pointer
   188 				return ctx->data[ctx->data_pos++];
   189 			} else {
   190 				return 0xff;
   191 			}
   193 		default:
   194 			// shut up annoying compilers which don't recognise unreachable code when they see it
   195 			// (here's looking at you, gcc!)
   196 			return 0xff;
   197 	}
   198 }
   201 void wd2797_write_reg(WD2797_CTX *ctx, uint8_t addr, uint8_t val)
   202 {
   203 	uint8_t cmd = val & CMD_MASK;
   204 	size_t lba;
   205 	bool is_type1 = false;
   206 	int temp;
   208 	switch (addr) {
   209 		case WD2797_REG_COMMAND:	// Command register
   210 			// write to command register clears interrupt request
   211 			ctx->irql = false;
   213 			// Is the drive ready?
   214 			if (ctx->disc_image == NULL) {
   215 				// No disc image, thus the drive is busy.
   216 				ctx->status = 0x80;
   217 				return;
   218 			}
   220 			// Handle Type 1 commands
   221 			switch (cmd) {
   222 				case CMD_RESTORE:
   223 					// Restore. Set track to 0 and throw an IRQ.
   224 					is_type1 = true;
   225 					ctx->track = 0;
   226 					break;
   228 				case CMD_SEEK:
   229 					// Seek. Seek to the track specced in the Data Register.
   230 					is_type1 = true;
   231 					if (ctx->data_reg < ctx->geom_tracks) {
   232 						ctx->track = ctx->data_reg;
   233 					} else {
   234 						// Seek error. :(
   235 						ctx->status = 0x10;
   236 					}
   238 				case CMD_STEP:
   239 					// TODO! deal with trk0!
   240 					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   241 					is_type1 = true;
   242 					break;
   244 				case CMD_STEPIN:
   245 				case CMD_STEPOUT:
   246 					// TODO! deal with trk0!
   247 					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   248 					if (cmd == CMD_STEPIN) {
   249 						ctx->last_step_dir = 1;
   250 					} else {
   251 						ctx->last_step_dir = -1;
   252 					}
   253 					is_type1 = true;
   254 					break;
   256 				case CMD_STEP_TU:
   257 				case CMD_STEPIN_TU:
   258 				case CMD_STEPOUT_TU:
   259 					// if this is a Step In or Step Out cmd, set the step-direction
   260 					if (cmd == CMD_STEPIN_TU) {
   261 						ctx->last_step_dir = 1;
   262 					} else if (cmd == CMD_STEPOUT_TU) {
   263 						ctx->last_step_dir = -1;
   264 					}
   266 					// Seek one step in the last direction used.
   267 					ctx->track += ctx->last_step_dir;
   268 					if (ctx->track < 0) ctx->track = 0;
   269 					if (ctx->track >= ctx->geom_tracks) {
   270 						// Seek past end of disc... that'll be a Seek Error then.
   271 						ctx->status = 0x10;
   272 						ctx->track = ctx->geom_tracks - 1;
   273 					}
   274 					is_type1 = true;
   275 					break;
   277 				default:
   278 					break;
   279 			}
   281 			if (is_type1) {
   282 				// Terminate any sector reads or writes
   283 				ctx->data_len = ctx->data_pos = 0;
   285 				// No DRQ bit for these commands.
   286 				ctx->cmd_has_drq = false;
   288 				// Type1 status byte...
   289 				ctx->status = 0;
   290 				// S7 = Not Ready. Command executed, therefore the drive was ready... :)
   291 				// S6 = Write Protect. TODO: add this
   292 				// S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
   293 				ctx->status |= 0x20;
   294 				// S4 = Seek Error. Not bloody likely if we got down here...!
   295 				// S3 = CRC Error. Not gonna happen on a disc image!
   296 				// S2 = Track 0
   297 				ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
   298 				// S1 = Index Pulse. TODO -- need periodics to emulate this
   299 				// S0 = Busy. We just exec'd the command, thus we're not busy.
   300 				// 		TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
   302 				// Set IRQ only if IRQL has been cleared (no pending IRQs)
   303 				ctx->irqe = !ctx->irql;
   304 				ctx->irql = true;
   305 				return;
   306 			}
   308 			// That's the Type 1 (seek) commands sorted. Now for the others.
   310 			// If drive isn't ready, then set status B7 and exit
   311 			if (ctx->disc_image == NULL) {
   312 				ctx->status = 0x80;
   313 				return;
   314 			}
   316 			// If this is a Write command, check write protect status too
   317 			// TODO!
   318 			if (false) {
   319 				// Write protected disc...
   320 				if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
   321 					// Set Write Protect bit and bail.
   322 					ctx->status = 0x40;
   324 					// Set IRQ only if IRQL has been cleared (no pending IRQs)
   325 					ctx->irqe = !ctx->irql;
   326 					ctx->irql = true;
   328 					return;
   329 				}
   330 			}
   332 			// Disc is ready to go. Parse the command word.
   333 			switch (cmd) {
   334 				case CMD_READ_ADDRESS:
   335 					// Read Address
   337 					// reset data pointers
   338 					ctx->data_pos = ctx->data_len = 0;
   340 					// load data buffer
   341 					ctx->data[ctx->data_len++] = ctx->track;
   342 					ctx->data[ctx->data_len++] = ctx->head;
   343 					ctx->data[ctx->data_len++] = ctx->sector;
   344 					switch (ctx->geom_secsz) {
   345 						case 128:	ctx->data[ctx->data_len++] = 0; break;
   346 						case 256:	ctx->data[ctx->data_len++] = 1; break;
   347 						case 512:	ctx->data[ctx->data_len++] = 2; break;
   348 						case 1024:	ctx->data[ctx->data_len++] = 3; break;
   349 						default:	ctx->data[ctx->data_len++] = 0xFF; break;	// TODO: deal with invalid values better
   350 					}
   351 					ctx->data[ctx->data_len++] = 0;	// TODO: IDAM CRC!
   352 					ctx->data[ctx->data_len++] = 0;
   354 					// B6, B5 = 0
   355 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   356 					// B3 = CRC Error. Not possible.
   357 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   358 					// B1 = DRQ. Data request.
   359 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   360 					break;
   362 				case CMD_READ_SECTOR:
   363 				case CMD_READ_SECTOR_MULTI:
   364 					// Read Sector or Read Sector Multiple
   365 					// reset data pointers
   366 					ctx->data_pos = ctx->data_len = 0;
   368 					// Calculate number of sectors to read from disc
   369 					if (cmd == CMD_READ_SECTOR_MULTI)
   370 						temp = ctx->geom_spt;
   371 					else
   372 						temp = 1;
   374 					for (int i=0; i<temp; i++) {
   375 						// Calculate the LBA address of the required sector
   376 						lba = ((((ctx->track * ctx->geom_heads) + ctx->head) * ctx->geom_spt) + ((ctx->sector + i - 1) % ctx->geom_spt)) * ctx->geom_secsz;
   378 						// Read the sector from the file
   379 						fseek(ctx->disc_image, lba, SEEK_SET);
   380 						ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
   381 						// TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   382 					}
   384 					// B6 = 0
   385 					// B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
   386 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   387 					// B3 = CRC Error. Not possible.
   388 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   389 					// B1 = DRQ. Data request.
   390 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   391 					break;
   393 				case CMD_READ_TRACK:
   394 					// Read Track
   395 					// TODO! implement this
   396 					// B6, B5, B4, B3 = 0
   397 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   398 					// B1 = DRQ. Data request.
   399 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   400 					break;
   402 				case CMD_WRITE_SECTOR:
   403 				case CMD_WRITE_SECTOR_MULTI:
   404 					// Write Sector or Write Sector Multiple
   406 					// reset data pointers
   407 					ctx->data_pos = ctx->data_len = 0;
   409 					// TODO: set "write pending" flag, and write LBA, and go from there.
   411 					// B6 = Write Protect. FIXME -- emulate this!
   412 					// B5 = 0
   413 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   414 					// B3 = CRC Error. Not possible.
   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_FORMAT_TRACK:
   421 					// Write Track (aka Format Track)
   422 					// B6 = Write Protect. FIXME -- emulate this!
   423 					// 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_FORCE_INTERRUPT:
   430 					// Force Interrupt...
   431 					// Terminates current operation and sends an interrupt
   432 					// TODO!
   433 					ctx->data_pos = ctx->data_len = 0;
   434 					// Set IRQ only if IRQL has been cleared (no pending IRQs)
   435 					ctx->irqe = !ctx->irql;
   436 					ctx->irql = true;
   437 					break;
   438 			}
   439 			break;
   441 		case WD2797_REG_TRACK:		// Track register
   442 			ctx->track = val;
   443 			break;
   445 		case WD2797_REG_SECTOR:		// Sector register
   446 			ctx->sector = val;
   447 			break;
   449 		case WD2797_REG_DATA:		// Data register
   450 			// Save the value written into the data register
   451 			ctx->data_reg = val;
   453 			// If we're processing a write command, and there's space in the
   454 			// buffer, allow the write.
   455 			if (ctx->data_pos < ctx->data_len) {
   456 				// store data byte and increment pointer
   457 				ctx->data[ctx->data_pos++] = val;
   458 			}
   459 			break;
   460 	}
   461 }