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