src/wd279x.c

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
child 49
545798274dad
permissions
-rw-r--r--

add preliminary WD2797 FDC emulator

     1 #include <stdint.h>
     2 #include <stdbool.h>
     3 #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 /**
    29  * @brief	Initialise a WD2797 context.
    30  */
    31 void wd2797_init(WD279X_CTX *ctx)
    32 {
    33 	// track, head and sector unknown
    34 	ctx->track = 0;
    35 	ctx->head = 0;
    36 	ctx->sector = 0;
    38 	// no IRQ or DRQ, no IRQ callback
    39 	ctx->irql = false;
    40 	ctx->irqe = false;
    42 	// no data available
    43 	ctx->data_pos = 0;
    44 	ctx->data_len = 0;
    46 	// No disc image loaded
    47 	ctx->disc_image = NULL;
    48 }
    51 /**
    52  * @brief	Read IRQ Rising Edge status. Clears Rising Edge status if it is set.
    53  * @note	No more IRQs will be sent until the Status Register is read, or a new command is written to the CR.
    54  */
    55 bool wd279x_get_irq(WD279X_CTX *ctx)
    56 {
    57 	// If an IRQ is pending, clear it and return true, otherwise return false
    58 	if (ctx->irqe) {
    59 		ctx->irqe = false;
    60 		return true;
    61 	} else {
    62 		return false;
    63 	}
    64 }
    67 /**
    68  * @brief	Read DRQ status.
    69  */
    70 bool wd279x_get_drq(WD279X_CTX *ctx)
    71 {
    72 	return (ctx->data_pos < ctx->data_len);
    73 }
    76 /**
    77  * @brief	Read WD279x register.
    78  */
    79 uint8_t wd279x_read_reg(WD279X_CTX *ctx, uint8_t addr)
    80 {
    81 	uint8_t temp = 0;
    83 	switch (addr & 0x03) {
    84 		case WD279X_REG_STATUS:		// Status register
    85 			// Read from status register clears IRQ
    86 			ctx->irql = false;
    88 			// Get current status flags (set by last command)
    89 			temp = ctx->status;
    90 			// DRQ bit
    91 			if (ctx->cmd_has_drq)
    92 				temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
    93 			// FDC is busy if there is still data in the buffer
    94 			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!
    95 																		// TODO: also if seek delay / read delay hasn't passed (but that's for later)
    96 			return temp;
    98 		case WD279X_REG_TRACK:		// Track register
    99 			return ctx->track;
   101 		case WD279X_REG_SECTOR:		// Sector register
   102 			return ctx->sector;
   104 		case WD279X_REG_DATA:		// Data register
   105 			// If there's data in the buffer, return it. Otherwise return 0xFF.
   106 			if (ctx->data_pos < ctx->data_len) {
   107 				// return data byte and increment pointer
   108 				return ctx->data[ctx->data_pos++];
   109 			} else {
   110 				return 0xff;
   111 			}
   113 		default:
   114 			// shut up annoying compilers which don't recognise unreachable code when they see it
   115 			// (here's looking at you, gcc!)
   116 			return 0xff;
   117 	}
   118 }
   121 /**
   122  * Write WD279X register
   123  */
   124 void wd279x_write_reg(WD279X_CTX *ctx, uint8_t addr, uint8_t val)
   125 {
   126 	uint8_t cmd = val & CMD_MASK;
   127 	size_t lba;
   128 	bool is_type1 = false;
   130 	switch (addr) {
   131 		case WD279X_REG_COMMAND:	// Command register
   132 			// write to command register clears interrupt request
   133 			ctx->irql = false;
   135 			// Is the drive ready?
   136 			if (ctx->disc_image == NULL) {
   137 				// No disc image, thus the drive is busy.
   138 				ctx->status = 0x80;
   139 				return;
   140 			}
   142 			// Handle Type 1 commands
   143 			switch (cmd) {
   144 				case CMD_RESTORE:
   145 					// Restore. Set track to 0 and throw an IRQ.
   146 					is_type1 = true;
   147 					ctx->track = 0;
   148 					break;
   150 				case CMD_SEEK:
   151 					// Seek. Seek to the track specced in the Data Register.
   152 					is_type1 = true;
   153 					if (ctx->data_reg < ctx->geom_tracks) {
   154 						ctx->track = ctx->data_reg;
   155 					} else {
   156 						// Seek error. :(
   157 						ctx->status = 0x10;
   158 					}
   160 				case CMD_STEP:
   161 					// TODO! deal with trk0!
   162 					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   163 					is_type1 = true;
   164 					break;
   166 				case CMD_STEPIN:
   167 				case CMD_STEPOUT:
   168 					// TODO! deal with trk0!
   169 					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   170 					if (cmd == CMD_STEPIN) {
   171 						ctx->last_step_dir = 1;
   172 					} else {
   173 						ctx->last_step_dir = -1;
   174 					}
   175 					is_type1 = true;
   176 					break;
   178 				case CMD_STEP_TU:
   179 				case CMD_STEPIN_TU:
   180 				case CMD_STEPOUT_TU:
   181 					// if this is a Step In or Step Out cmd, set the step-direction
   182 					if (cmd == CMD_STEPIN_TU) {
   183 						ctx->last_step_dir = 1;
   184 					} else if (cmd == CMD_STEPOUT_TU) {
   185 						ctx->last_step_dir = -1;
   186 					}
   188 					// Seek one step in the last direction used.
   189 					ctx->track += ctx->last_step_dir;
   190 					if (ctx->track < 0) ctx->track = 0;
   191 					if (ctx->track >= ctx->geom_tracks) {
   192 						// Seek past end of disc... that'll be a Seek Error then.
   193 						ctx->status = 0x10;
   194 						ctx->track = ctx->geom_tracks - 1;
   195 					}
   196 					is_type1 = true;
   197 					break;
   199 				default:
   200 					break;
   201 			}
   203 			if (is_type1) {
   204 				// Terminate any sector reads or writes
   205 				ctx->data_len = ctx->data_pos = 0;
   207 				// No DRQ bit for these commands.
   208 				ctx->cmd_has_drq = false;
   210 				// Type1 status byte...
   211 				ctx->status = 0;
   212 				// S7 = Not Ready. Command executed, therefore the drive was ready... :)
   213 				// S6 = Write Protect. TODO: add this
   214 				// S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
   215 				ctx->status |= 0x20;
   216 				// S4 = Seek Error. Not bloody likely if we got down here...!
   217 				// S3 = CRC Error. Not gonna happen on a disc image!
   218 				// S2 = Track 0
   219 				ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
   220 				// S1 = Index Pulse. TODO -- need periodics to emulate this
   221 				// S0 = Busy. We just exec'd the command, thus we're not busy.
   222 				// 		TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
   224 				// Set IRQ only if IRQL has been cleared (no pending IRQs)
   225 				ctx->irqe = !ctx->irql;
   226 				ctx->irql = true;
   227 				return;
   228 			}
   230 			// That's the Type 1 (seek) commands sorted. Now for the others.
   232 			// If drive isn't ready, then set status B7 and exit
   233 			if (ctx->disc_image == NULL) {
   234 				ctx->status = 0x80;
   235 				return;
   236 			}
   238 			// If this is a Write command, check write protect status too
   239 			// TODO!
   240 			if (false) {
   241 				// Write protected disc...
   242 				if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
   243 					// Set Write Protect bit and bail.
   244 					ctx->status = 0x40;
   246 					// Set IRQ only if IRQL has been cleared (no pending IRQs)
   247 					ctx->irqe = !ctx->irql;
   248 					ctx->irql = true;
   250 					return;
   251 				}
   252 			}
   254 			// Disc is ready to go. Parse the command word.
   255 			switch (cmd) {
   256 				case CMD_READ_ADDRESS:
   257 					// Read Address
   259 					// reset data pointers
   260 					ctx->data_pos = ctx->data_len = 0;
   262 					// load data buffer
   263 					ctx->data[ctx->data_len++] = ctx->track;
   264 					ctx->data[ctx->data_len++] = ctx->head;
   265 					ctx->data[ctx->data_len++] = ctx->sector;
   266 					switch (ctx->geom_secsz) {
   267 						case 128:	ctx->data[ctx->data_len++] = 0; break;
   268 						case 256:	ctx->data[ctx->data_len++] = 1; break;
   269 						case 512:	ctx->data[ctx->data_len++] = 2; break;
   270 						case 1024:	ctx->data[ctx->data_len++] = 3; break;
   271 						default:	ctx->data[ctx->data_len++] = 0xFF; break;	// TODO: deal with invalid values better
   272 					}
   273 					ctx->data[ctx->data_len++] = 0;	// TODO: IDAM CRC!
   274 					ctx->data[ctx->data_len++] = 0;
   276 					// B6, B5 = 0
   277 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   278 					// B3 = CRC Error. Not possible.
   279 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   280 					// B1 = DRQ. Data request.
   281 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   282 					break;
   284 				case CMD_READ_SECTOR:
   285 				case CMD_READ_SECTOR_MULTI:
   286 					// Read Sector or Read Sector Multiple
   287 					// TODO: multiple is not implemented!
   288 					if (cmd == CMD_READ_SECTOR_MULTI) printf("WD279X: NOTIMP: read-multi\n");
   290 					// reset data pointers
   291 					ctx->data_pos = ctx->data_len = 0;
   293 					// Calculate the LBA address of the required sector
   294 					lba = ((((ctx->track * ctx->geom_heads) + ctx->head) * ctx->geom_spt) + ctx->sector - 1) * ctx->geom_secsz;
   296 					// Read the sector from the file
   297 					fseek(ctx->disc_image, lba, SEEK_SET);
   298 					ctx->data_len = fread(ctx->data, 1, ctx->geom_secsz, ctx->disc_image);
   299 					// TODO: if datalen < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   300 					// TODO: if we're asked to do a Read Multi, malloc for an entire track, then just read a full track...
   302 					// B6 = 0
   303 					// B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
   304 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   305 					// B3 = CRC Error. Not possible.
   306 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   307 					// B1 = DRQ. Data request.
   308 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   309 					break;
   311 				case CMD_READ_TRACK:
   312 					// Read Track
   313 					// B6, B5, B4, B3 = 0
   314 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   315 					// B1 = DRQ. Data request.
   316 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   317 					break;
   319 				case CMD_WRITE_SECTOR:
   320 				case CMD_WRITE_SECTOR_MULTI:
   321 					// Write Sector or Write Sector Multiple
   323 					// reset data pointers
   324 					ctx->data_pos = ctx->data_len = 0;
   326 					// TODO: set "write pending" flag, and write LBA, and go from there.
   328 					// B6 = Write Protect. FIXME -- emulate this!
   329 					// B5 = 0
   330 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   331 					// B3 = CRC Error. Not possible.
   332 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   333 					// B1 = DRQ. Data request.
   334 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   335 					break;
   337 				case CMD_FORMAT_TRACK:
   338 					// Write Track (aka Format Track)
   339 					// B6 = Write Protect. FIXME -- emulate this!
   340 					// B5, B4, B3 = 0
   341 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   342 					// B1 = DRQ. Data request.
   343 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   344 					break;
   346 				case CMD_FORCE_INTERRUPT:
   347 					// Force Interrupt...
   348 					// TODO: terminate current R/W op
   349 					// TODO: variants for this command
   350 					break;
   351 			}
   352 			break;
   354 		case WD279X_REG_TRACK:		// Track register
   355 			ctx->track = val;
   356 			break;
   358 		case WD279X_REG_SECTOR:		// Sector register
   359 			ctx->sector = val;
   360 			break;
   362 		case WD279X_REG_DATA:		// Data register
   363 			// Save the value written into the data register
   364 			ctx->data_reg = val;
   366 			// If we're processing a write command, and there's space in the
   367 			// buffer, allow the write.
   368 			if (ctx->data_pos < ctx->data_len) {
   369 				// store data byte and increment pointer
   370 				ctx->data[ctx->data_pos++] = val;
   371 			}
   372 			break;
   373 	}
   374 }