add preliminary WD2797 FDC emulator

Sun, 05 Dec 2010 03:55:46 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Sun, 05 Dec 2010 03:55:46 +0000
changeset 48
d52688dad7fd
parent 47
c472ae8f44b2
child 49
545798274dad

add preliminary WD2797 FDC emulator

Makefile file | annotate | diff | revisions
src/main.c file | annotate | diff | revisions
src/wd279x.c file | annotate | diff | revisions
src/wd279x.h file | annotate | diff | revisions
     1.1 diff -r c472ae8f44b2 -r d52688dad7fd Makefile
     1.2 --- a/Makefile	Fri Dec 03 14:21:19 2010 +0000
     1.3 +++ b/Makefile	Sun Dec 05 03:55:46 2010 +0000
     1.4 @@ -118,7 +118,7 @@
     1.5  TARGET		=	freebee
     1.6  
     1.7  # source files that produce object files
     1.8 -SRC			=	main.c state.c memory.c
     1.9 +SRC			=	main.c state.c memory.c wd279x.c
    1.10  SRC			+=	musashi/m68kcpu.c musashi/m68kdasm.c musashi/m68kops.c musashi/m68kopac.c musashi/m68kopdm.c musashi/m68kopnz.c
    1.11  
    1.12  # source type - either "c" or "cpp" (C or C++)
     2.1 diff -r c472ae8f44b2 -r d52688dad7fd src/main.c
     2.2 --- a/src/main.c	Fri Dec 03 14:21:19 2010 +0000
     2.3 +++ b/src/main.c	Sun Dec 05 03:55:46 2010 +0000
     2.4 @@ -129,9 +129,9 @@
     2.5  				return true;
     2.6  			case SDL_KEYDOWN:
     2.7  				switch (event.key.keysym.sym) {
     2.8 -					case SDLK_ESCAPE:
     2.9 +					case SDLK_F12:
    2.10  						if (event.key.keysym.mod & (KMOD_LALT | KMOD_RALT))
    2.11 -							// ALT-ESC pressed; exit emulator
    2.12 +							// ALT-F12 pressed; exit emulator
    2.13  							return true;
    2.14  						break;
    2.15  					default:
     3.1 diff -r c472ae8f44b2 -r d52688dad7fd src/wd279x.c
     3.2 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.3 +++ b/src/wd279x.c	Sun Dec 05 03:55:46 2010 +0000
     3.4 @@ -0,0 +1,375 @@
     3.5 +#include <stdint.h>
     3.6 +#include <stdbool.h>
     3.7 +#include "wd279x.h"
     3.8 +
     3.9 +
    3.10 +/// WD2797 command constants
    3.11 +enum {
    3.12 +	CMD_MASK				= 0xF0,		///< Bit mask to detect command bits
    3.13 +	CMD_RESTORE				= 0x00,		///< Restore (recalibrate, seek to track 0)
    3.14 +	CMD_SEEK				= 0x10,		///< Seek to given track
    3.15 +	CMD_STEP				= 0x20,		///< Step
    3.16 +	CMD_STEP_TU				= 0x30,		///< Step and update track register
    3.17 +	CMD_STEPIN				= 0x40,		///< Step In
    3.18 +	CMD_STEPIN_TU			= 0x50,		///< Step In and update track register
    3.19 +	CMD_STEPOUT				= 0x60,		///< Step Out
    3.20 +	CMD_STEPOUT_TU			= 0x70,		///< Step Out and update track register
    3.21 +	CMD_READ_SECTOR			= 0x80,		///< Read Sector
    3.22 +	CMD_READ_SECTOR_MULTI	= 0x90,		///< Read Multiple Sectors
    3.23 +	CMD_WRITE_SECTOR		= 0xA0,		///< Write Sector
    3.24 +	CMD_WRITE_SECTOR_MULTI	= 0xB0,		///< Write Multiple Sectors
    3.25 +	CMD_READ_ADDRESS		= 0xC0,		///< Read Address (IDAM contents)
    3.26 +	CMD_FORCE_INTERRUPT		= 0xD0,		///< Force Interrupt
    3.27 +	CMD_READ_TRACK			= 0xE0,		///< Read Track
    3.28 +	CMD_FORMAT_TRACK		= 0xF0		///< Format Track
    3.29 +};
    3.30 +
    3.31 +
    3.32 +/**
    3.33 + * @brief	Initialise a WD2797 context.
    3.34 + */
    3.35 +void wd2797_init(WD279X_CTX *ctx)
    3.36 +{
    3.37 +	// track, head and sector unknown
    3.38 +	ctx->track = 0;
    3.39 +	ctx->head = 0;
    3.40 +	ctx->sector = 0;
    3.41 +
    3.42 +	// no IRQ or DRQ, no IRQ callback
    3.43 +	ctx->irql = false;
    3.44 +	ctx->irqe = false;
    3.45 +
    3.46 +	// no data available
    3.47 +	ctx->data_pos = 0;
    3.48 +	ctx->data_len = 0;
    3.49 +
    3.50 +	// No disc image loaded
    3.51 +	ctx->disc_image = NULL;
    3.52 +}
    3.53 +
    3.54 +
    3.55 +/**
    3.56 + * @brief	Read IRQ Rising Edge status. Clears Rising Edge status if it is set.
    3.57 + * @note	No more IRQs will be sent until the Status Register is read, or a new command is written to the CR.
    3.58 + */
    3.59 +bool wd279x_get_irq(WD279X_CTX *ctx)
    3.60 +{
    3.61 +	// If an IRQ is pending, clear it and return true, otherwise return false
    3.62 +	if (ctx->irqe) {
    3.63 +		ctx->irqe = false;
    3.64 +		return true;
    3.65 +	} else {
    3.66 +		return false;
    3.67 +	}
    3.68 +}
    3.69 +
    3.70 +
    3.71 +/**
    3.72 + * @brief	Read DRQ status.
    3.73 + */
    3.74 +bool wd279x_get_drq(WD279X_CTX *ctx)
    3.75 +{
    3.76 +	return (ctx->data_pos < ctx->data_len);
    3.77 +}
    3.78 +
    3.79 +
    3.80 +/**
    3.81 + * @brief	Read WD279x register.
    3.82 + */
    3.83 +uint8_t wd279x_read_reg(WD279X_CTX *ctx, uint8_t addr)
    3.84 +{
    3.85 +	uint8_t temp = 0;
    3.86 +
    3.87 +	switch (addr & 0x03) {
    3.88 +		case WD279X_REG_STATUS:		// Status register
    3.89 +			// Read from status register clears IRQ
    3.90 +			ctx->irql = false;
    3.91 +
    3.92 +			// Get current status flags (set by last command)
    3.93 +			temp = ctx->status;
    3.94 +			// DRQ bit
    3.95 +			if (ctx->cmd_has_drq)
    3.96 +				temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
    3.97 +			// FDC is busy if there is still data in the buffer
    3.98 +			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!
    3.99 +																		// TODO: also if seek delay / read delay hasn't passed (but that's for later)
   3.100 +			return temp;
   3.101 +
   3.102 +		case WD279X_REG_TRACK:		// Track register
   3.103 +			return ctx->track;
   3.104 +
   3.105 +		case WD279X_REG_SECTOR:		// Sector register
   3.106 +			return ctx->sector;
   3.107 +
   3.108 +		case WD279X_REG_DATA:		// Data register
   3.109 +			// If there's data in the buffer, return it. Otherwise return 0xFF.
   3.110 +			if (ctx->data_pos < ctx->data_len) {
   3.111 +				// return data byte and increment pointer
   3.112 +				return ctx->data[ctx->data_pos++];
   3.113 +			} else {
   3.114 +				return 0xff;
   3.115 +			}
   3.116 +
   3.117 +		default:
   3.118 +			// shut up annoying compilers which don't recognise unreachable code when they see it
   3.119 +			// (here's looking at you, gcc!)
   3.120 +			return 0xff;
   3.121 +	}
   3.122 +}
   3.123 +
   3.124 +
   3.125 +/**
   3.126 + * Write WD279X register
   3.127 + */
   3.128 +void wd279x_write_reg(WD279X_CTX *ctx, uint8_t addr, uint8_t val)
   3.129 +{
   3.130 +	uint8_t cmd = val & CMD_MASK;
   3.131 +	size_t lba;
   3.132 +	bool is_type1 = false;
   3.133 +
   3.134 +	switch (addr) {
   3.135 +		case WD279X_REG_COMMAND:	// Command register
   3.136 +			// write to command register clears interrupt request
   3.137 +			ctx->irql = false;
   3.138 +
   3.139 +			// Is the drive ready?
   3.140 +			if (ctx->disc_image == NULL) {
   3.141 +				// No disc image, thus the drive is busy.
   3.142 +				ctx->status = 0x80;
   3.143 +				return;
   3.144 +			}
   3.145 +
   3.146 +			// Handle Type 1 commands
   3.147 +			switch (cmd) {
   3.148 +				case CMD_RESTORE:
   3.149 +					// Restore. Set track to 0 and throw an IRQ.
   3.150 +					is_type1 = true;
   3.151 +					ctx->track = 0;
   3.152 +					break;
   3.153 +
   3.154 +				case CMD_SEEK:
   3.155 +					// Seek. Seek to the track specced in the Data Register.
   3.156 +					is_type1 = true;
   3.157 +					if (ctx->data_reg < ctx->geom_tracks) {
   3.158 +						ctx->track = ctx->data_reg;
   3.159 +					} else {
   3.160 +						// Seek error. :(
   3.161 +						ctx->status = 0x10;
   3.162 +					}
   3.163 +
   3.164 +				case CMD_STEP:
   3.165 +					// TODO! deal with trk0!
   3.166 +					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   3.167 +					is_type1 = true;
   3.168 +					break;
   3.169 +
   3.170 +				case CMD_STEPIN:
   3.171 +				case CMD_STEPOUT:
   3.172 +					// TODO! deal with trk0!
   3.173 +					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   3.174 +					if (cmd == CMD_STEPIN) {
   3.175 +						ctx->last_step_dir = 1;
   3.176 +					} else {
   3.177 +						ctx->last_step_dir = -1;
   3.178 +					}
   3.179 +					is_type1 = true;
   3.180 +					break;
   3.181 +
   3.182 +				case CMD_STEP_TU:
   3.183 +				case CMD_STEPIN_TU:
   3.184 +				case CMD_STEPOUT_TU:
   3.185 +					// if this is a Step In or Step Out cmd, set the step-direction
   3.186 +					if (cmd == CMD_STEPIN_TU) {
   3.187 +						ctx->last_step_dir = 1;
   3.188 +					} else if (cmd == CMD_STEPOUT_TU) {
   3.189 +						ctx->last_step_dir = -1;
   3.190 +					}
   3.191 +
   3.192 +					// Seek one step in the last direction used.
   3.193 +					ctx->track += ctx->last_step_dir;
   3.194 +					if (ctx->track < 0) ctx->track = 0;
   3.195 +					if (ctx->track >= ctx->geom_tracks) {
   3.196 +						// Seek past end of disc... that'll be a Seek Error then.
   3.197 +						ctx->status = 0x10;
   3.198 +						ctx->track = ctx->geom_tracks - 1;
   3.199 +					}
   3.200 +					is_type1 = true;
   3.201 +					break;
   3.202 +
   3.203 +				default:
   3.204 +					break;
   3.205 +			}
   3.206 +
   3.207 +			if (is_type1) {
   3.208 +				// Terminate any sector reads or writes
   3.209 +				ctx->data_len = ctx->data_pos = 0;
   3.210 +
   3.211 +				// No DRQ bit for these commands.
   3.212 +				ctx->cmd_has_drq = false;
   3.213 +
   3.214 +				// Type1 status byte...
   3.215 +				ctx->status = 0;
   3.216 +				// S7 = Not Ready. Command executed, therefore the drive was ready... :)
   3.217 +				// S6 = Write Protect. TODO: add this
   3.218 +				// S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
   3.219 +				ctx->status |= 0x20;
   3.220 +				// S4 = Seek Error. Not bloody likely if we got down here...!
   3.221 +				// S3 = CRC Error. Not gonna happen on a disc image!
   3.222 +				// S2 = Track 0
   3.223 +				ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
   3.224 +				// S1 = Index Pulse. TODO -- need periodics to emulate this
   3.225 +				// S0 = Busy. We just exec'd the command, thus we're not busy.
   3.226 +				// 		TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
   3.227 +				
   3.228 +				// Set IRQ only if IRQL has been cleared (no pending IRQs)
   3.229 +				ctx->irqe = !ctx->irql;
   3.230 +				ctx->irql = true;
   3.231 +				return;
   3.232 +			}
   3.233 +
   3.234 +			// That's the Type 1 (seek) commands sorted. Now for the others.
   3.235 +
   3.236 +			// If drive isn't ready, then set status B7 and exit
   3.237 +			if (ctx->disc_image == NULL) {
   3.238 +				ctx->status = 0x80;
   3.239 +				return;
   3.240 +			}
   3.241 +
   3.242 +			// If this is a Write command, check write protect status too
   3.243 +			// TODO!
   3.244 +			if (false) {
   3.245 +				// Write protected disc...
   3.246 +				if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
   3.247 +					// Set Write Protect bit and bail.
   3.248 +					ctx->status = 0x40;
   3.249 +
   3.250 +					// Set IRQ only if IRQL has been cleared (no pending IRQs)
   3.251 +					ctx->irqe = !ctx->irql;
   3.252 +					ctx->irql = true;
   3.253 +
   3.254 +					return;
   3.255 +				}
   3.256 +			}
   3.257 +
   3.258 +			// Disc is ready to go. Parse the command word.
   3.259 +			switch (cmd) {
   3.260 +				case CMD_READ_ADDRESS:
   3.261 +					// Read Address
   3.262 +
   3.263 +					// reset data pointers
   3.264 +					ctx->data_pos = ctx->data_len = 0;
   3.265 +
   3.266 +					// load data buffer
   3.267 +					ctx->data[ctx->data_len++] = ctx->track;
   3.268 +					ctx->data[ctx->data_len++] = ctx->head;
   3.269 +					ctx->data[ctx->data_len++] = ctx->sector;
   3.270 +					switch (ctx->geom_secsz) {
   3.271 +						case 128:	ctx->data[ctx->data_len++] = 0; break;
   3.272 +						case 256:	ctx->data[ctx->data_len++] = 1; break;
   3.273 +						case 512:	ctx->data[ctx->data_len++] = 2; break;
   3.274 +						case 1024:	ctx->data[ctx->data_len++] = 3; break;
   3.275 +						default:	ctx->data[ctx->data_len++] = 0xFF; break;	// TODO: deal with invalid values better
   3.276 +					}
   3.277 +					ctx->data[ctx->data_len++] = 0;	// TODO: IDAM CRC!
   3.278 +					ctx->data[ctx->data_len++] = 0;
   3.279 +
   3.280 +					// B6, B5 = 0
   3.281 +					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   3.282 +					// B3 = CRC Error. Not possible.
   3.283 +					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   3.284 +					// B1 = DRQ. Data request.
   3.285 +					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   3.286 +					break;
   3.287 +
   3.288 +				case CMD_READ_SECTOR:
   3.289 +				case CMD_READ_SECTOR_MULTI:
   3.290 +					// Read Sector or Read Sector Multiple
   3.291 +					// TODO: multiple is not implemented!
   3.292 +					if (cmd == CMD_READ_SECTOR_MULTI) printf("WD279X: NOTIMP: read-multi\n");
   3.293 +
   3.294 +					// reset data pointers
   3.295 +					ctx->data_pos = ctx->data_len = 0;
   3.296 +
   3.297 +					// Calculate the LBA address of the required sector
   3.298 +					lba = ((((ctx->track * ctx->geom_heads) + ctx->head) * ctx->geom_spt) + ctx->sector - 1) * ctx->geom_secsz;
   3.299 +
   3.300 +					// Read the sector from the file
   3.301 +					fseek(ctx->disc_image, lba, SEEK_SET);
   3.302 +					ctx->data_len = fread(ctx->data, 1, ctx->geom_secsz, ctx->disc_image);
   3.303 +					// TODO: if datalen < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   3.304 +					// TODO: if we're asked to do a Read Multi, malloc for an entire track, then just read a full track...
   3.305 +
   3.306 +					// B6 = 0
   3.307 +					// B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
   3.308 +					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   3.309 +					// B3 = CRC Error. Not possible.
   3.310 +					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   3.311 +					// B1 = DRQ. Data request.
   3.312 +					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   3.313 +					break;
   3.314 +
   3.315 +				case CMD_READ_TRACK:
   3.316 +					// Read Track
   3.317 +					// B6, B5, B4, B3 = 0
   3.318 +					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   3.319 +					// B1 = DRQ. Data request.
   3.320 +					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   3.321 +					break;
   3.322 +
   3.323 +				case CMD_WRITE_SECTOR:
   3.324 +				case CMD_WRITE_SECTOR_MULTI:
   3.325 +					// Write Sector or Write Sector Multiple
   3.326 +
   3.327 +					// reset data pointers
   3.328 +					ctx->data_pos = ctx->data_len = 0;
   3.329 +
   3.330 +					// TODO: set "write pending" flag, and write LBA, and go from there.
   3.331 +
   3.332 +					// B6 = Write Protect. FIXME -- emulate this!
   3.333 +					// B5 = 0
   3.334 +					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   3.335 +					// B3 = CRC Error. Not possible.
   3.336 +					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   3.337 +					// B1 = DRQ. Data request.
   3.338 +					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   3.339 +					break;
   3.340 +
   3.341 +				case CMD_FORMAT_TRACK:
   3.342 +					// Write Track (aka Format Track)
   3.343 +					// B6 = Write Protect. FIXME -- emulate this!
   3.344 +					// B5, B4, B3 = 0
   3.345 +					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   3.346 +					// B1 = DRQ. Data request.
   3.347 +					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   3.348 +					break;
   3.349 +
   3.350 +				case CMD_FORCE_INTERRUPT:
   3.351 +					// Force Interrupt...
   3.352 +					// TODO: terminate current R/W op
   3.353 +					// TODO: variants for this command
   3.354 +					break;
   3.355 +			}
   3.356 +			break;
   3.357 +
   3.358 +		case WD279X_REG_TRACK:		// Track register
   3.359 +			ctx->track = val;
   3.360 +			break;
   3.361 +
   3.362 +		case WD279X_REG_SECTOR:		// Sector register
   3.363 +			ctx->sector = val;
   3.364 +			break;
   3.365 +
   3.366 +		case WD279X_REG_DATA:		// Data register
   3.367 +			// Save the value written into the data register
   3.368 +			ctx->data_reg = val;
   3.369 +
   3.370 +			// If we're processing a write command, and there's space in the
   3.371 +			// buffer, allow the write.
   3.372 +			if (ctx->data_pos < ctx->data_len) {
   3.373 +				// store data byte and increment pointer
   3.374 +				ctx->data[ctx->data_pos++] = val;
   3.375 +			}
   3.376 +			break;
   3.377 +	}
   3.378 +}
   3.379 +
     4.1 diff -r c472ae8f44b2 -r d52688dad7fd src/wd279x.h
     4.2 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.3 +++ b/src/wd279x.h	Sun Dec 05 03:55:46 2010 +0000
     4.4 @@ -0,0 +1,42 @@
     4.5 +#ifndef _WD279X_H
     4.6 +#define _WD279X_H
     4.7 +
     4.8 +#include <stdbool.h>
     4.9 +#include <stddef.h>
    4.10 +#include <stdint.h>
    4.11 +#include <stdio.h>
    4.12 +
    4.13 +enum {
    4.14 +	WD279X_REG_STATUS = 0,
    4.15 +	WD279X_REG_COMMAND = 0,
    4.16 +	WD279X_REG_TRACK = 1,
    4.17 +	WD279X_REG_SECTOR = 2,
    4.18 +	WD279X_REG_DATA = 3
    4.19 +} WD279X_REG;
    4.20 +
    4.21 +typedef struct {
    4.22 +	// Current track, head and sector
    4.23 +	int						track, head, sector;
    4.24 +	// Geometry of current disc
    4.25 +	int						geom_secsz, geom_spt, geom_heads, geom_tracks;
    4.26 +	// IRQ status, level and edge sensitive.
    4.27 +	// Edge sensitive is cleared when host polls the IRQ status.
    4.28 +	// Level sensitive is cleared when emulated CPU polls the status reg or writes a new cmnd.
    4.29 +	// No EDGE sensitive interrupts will be issued unless the LEVEL SENSITIVE IRQ is clear.
    4.30 +	bool					irql, irqe;
    4.31 +	// Status of last command
    4.32 +	uint8_t					status;
    4.33 +	// Last command uses DRQ bit?
    4.34 +	bool					cmd_has_drq;
    4.35 +	// The last value written to the data register
    4.36 +	uint8_t					data_reg;
    4.37 +	// Last step direction. -1 for "towards zero", 1 for "away from zero"
    4.38 +	int						last_step_dir;
    4.39 +	// Data buffer, current DRQ pointer and length
    4.40 +	uint8_t					data[1024];
    4.41 +	size_t					data_pos, data_len;
    4.42 +	// Current disc image file
    4.43 +	FILE					*disc_image;
    4.44 +} WD279X_CTX;
    4.45 +
    4.46 +#endif