src/wd279x.c

Mon, 14 Jan 2013 09:50:37 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Mon, 14 Jan 2013 09:50:37 +0000
changeset 120
df40e6668a46
parent 111
4c85846b09cd
permissions
-rw-r--r--

Max out system memory by default

Set the system memory to 2MiB base, 2MiB ext. This is a fully loaded 3B1
motherboard with a RAM expansion board. 512KiB base/no ext is the minimum
which can be specified (e.g. kernel memory map area only) but does not leave
any room for userspace. The kernel doesn't like that and doesn't handle it
gracefully...!

     1 #include <stdint.h>
     2 #include <stdbool.h>
     3 #include <malloc.h>
     4 #include "musashi/m68k.h"
     5 #include "wd279x.h"
     7 #ifndef WD279X_DEBUG
     8 #define NDEBUG
     9 #endif
    10 #include "utils.h"
    12 /// WD2797 command constants
    13 enum {
    14 	CMD_MASK				= 0xF0,		///< Bit mask to detect command bits
    15 	CMD_RESTORE				= 0x00,		///< Restore (recalibrate, seek to track 0)
    16 	CMD_SEEK				= 0x10,		///< Seek to given track
    17 	CMD_STEP				= 0x20,		///< Step
    18 	CMD_STEP_TU				= 0x30,		///< Step and update track register
    19 	CMD_STEPIN				= 0x40,		///< Step In
    20 	CMD_STEPIN_TU			= 0x50,		///< Step In and update track register
    21 	CMD_STEPOUT				= 0x60,		///< Step Out
    22 	CMD_STEPOUT_TU			= 0x70,		///< Step Out and update track register
    23 	CMD_READ_SECTOR			= 0x80,		///< Read Sector
    24 	CMD_READ_SECTOR_MULTI	= 0x90,		///< Read Multiple Sectors
    25 	CMD_WRITE_SECTOR		= 0xA0,		///< Write Sector
    26 	CMD_WRITE_SECTOR_MULTI	= 0xB0,		///< Write Multiple Sectors
    27 	CMD_READ_ADDRESS		= 0xC0,		///< Read Address (IDAM contents)
    28 	CMD_FORCE_INTERRUPT		= 0xD0,		///< Force Interrupt
    29 	CMD_READ_TRACK			= 0xE0,		///< Read Track
    30 	CMD_FORMAT_TRACK		= 0xF0		///< Format Track
    31 };
    34 void wd2797_init(WD2797_CTX *ctx)
    35 {
    36 	// track, head and sector unknown
    37 	ctx->track = ctx->head = ctx->sector = 0;
    38 	ctx->track_reg = 0;
    40 	// no IRQ pending
    41 	ctx->irq = false;
    43 	// no data available
    44 	ctx->data_pos = ctx->data_len = 0;
    45 	ctx->data = NULL;
    47 	// Status register clear, not busy; type1 command
    48 	ctx->status = 0;
    49 	ctx->cmd_has_drq = false;
    51 	// No format command in progress
    52 	ctx->formatting = false;
    54 	// Clear data register
    55 	ctx->data_reg = 0;
    57 	// Last step direction = "towards zero"
    58 	ctx->last_step_dir = -1;
    60 	// No disc image loaded
    61 	ctx->disc_image = NULL;
    62 	ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = ctx->geom_tracks = 0;
    63 }
    66 void wd2797_reset(WD2797_CTX *ctx)
    67 {
    68 	// track, head and sector unknown
    69 	ctx->track = ctx->head = ctx->sector = 0;
    70 	ctx->track_reg = 0;
    72 	// no IRQ pending
    73 	ctx->irq = false;
    75 	// no data available
    76 	ctx->data_pos = ctx->data_len = 0;
    78 	// Status register clear, not busy
    79 	ctx->status = 0;
    81 	// Clear data register
    82 	ctx->data_reg = 0;
    84 	// Last step direction
    85 	ctx->last_step_dir = -1;
    86 }
    89 void wd2797_done(WD2797_CTX *ctx)
    90 {
    91 	// Reset the WD2797
    92 	wd2797_reset(ctx);
    94 	// Free any allocated memory
    95 	if (ctx->data) {
    96 		free(ctx->data);
    97 		ctx->data = NULL;
    98 	}
    99 }
   102 bool wd2797_get_irq(WD2797_CTX *ctx)
   103 {
   104 	return ctx->irq;
   105 }
   107 bool wd2797_get_drq(WD2797_CTX *ctx)
   108 {
   109 	return (ctx->data_pos < ctx->data_len);
   110 }
   113 WD2797_ERR wd2797_load(WD2797_CTX *ctx, FILE *fp, int secsz, int spt, int heads, int writeable)
   114 {
   115 	size_t filesize;
   117 	// Start by finding out how big the image file is
   118 	fseek(fp, 0, SEEK_END);
   119 	filesize = ftell(fp);
   120 	fseek(fp, 0, SEEK_SET);
   122 	// Now figure out how many tracks it contains
   123 	int tracks = filesize / secsz / spt / heads;
   124 	// Confirm...
   125 	if (tracks < 1) {
   126 		return WD2797_ERR_BAD_GEOM;
   127 	}
   129 	// Allocate enough memory to store one disc track
   130 	if (ctx->data) {
   131 		free(ctx->data);
   132 	}
   133 	ctx->data = malloc(secsz * spt);
   134 	if (!ctx->data)
   135 		return WD2797_ERR_NO_MEMORY;
   137 	// Load the image and the geometry data
   138 	ctx->disc_image = fp;
   139 	ctx->geom_tracks = tracks;
   140 	ctx->geom_secsz = secsz;
   141 	ctx->geom_heads = heads;
   142 	ctx->geom_spt = spt;
   143 	ctx->writeable = writeable;
   144 	return WD2797_ERR_OK;
   145 }
   148 void wd2797_unload(WD2797_CTX *ctx)
   149 {
   150 	// Free memory buffer
   151 	if (ctx->data) {
   152 		free(ctx->data);
   153 		ctx->data = NULL;
   154 	}
   156 	// Clear file pointer
   157 	ctx->disc_image = NULL;
   159 	// Clear the disc geometry
   160 	ctx->geom_tracks = ctx->geom_secsz = ctx->geom_spt = ctx->geom_heads = 0;
   161 }
   164 uint8_t wd2797_read_reg(WD2797_CTX *ctx, uint8_t addr)
   165 {
   166 	uint8_t temp = 0;
   167 	m68k_end_timeslice();
   169 	switch (addr & 0x03) {
   170 		case WD2797_REG_STATUS:		// Status register
   171 			// Read from status register clears IRQ
   172 			ctx->irq = false;
   174 			// Get current status flags (set by last command)
   175 			// DRQ bit
   176 			if (ctx->cmd_has_drq) {
   177 				temp = ctx->status & ~0x03;
   178 				temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   179 				LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
   180 			} else {
   181 				temp = ctx->status & ~0x01;
   182 			}
   183 			// FDC is busy if there is still data in the buffer
   184 			temp |= (ctx->data_pos < ctx->data_len) ? 0x81 : 0x00;	// if data in buffer, then DMA hasn't copied it yet, and we're still busy!
   185 																	// TODO: also if seek delay / read delay hasn't passed (but that's for later)
   186 			return temp;
   188 		case WD2797_REG_TRACK:		// Track register
   189 			return ctx->track_reg;
   191 		case WD2797_REG_SECTOR:		// Sector register
   192 			return ctx->sector;
   194 		case WD2797_REG_DATA:		// Data register
   195 			// If there's data in the buffer, return it. Otherwise return 0xFF.
   196 			if (ctx->data_pos < ctx->data_len) {
   197 				// set IRQ if this is the last data byte
   198 				if (ctx->data_pos == (ctx->data_len-1)) {
   199 					// Set IRQ
   200 					ctx->irq = true;
   201 				}
   202 				// return data byte and increment pointer
   203 				return ctx->data[ctx->data_pos++];
   204 			} else {
   205 				// command finished
   206 				return ctx->data_reg;
   207 			}
   209 		default:
   210 			// shut up annoying compilers which don't recognise unreachable code when they see it
   211 			// (here's looking at you, gcc!)
   212 			return 0xff;
   213 	}
   214 }
   217 void wd2797_write_reg(WD2797_CTX *ctx, uint8_t addr, uint8_t val)
   218 {
   219 	uint8_t cmd = val & CMD_MASK;
   220 	size_t lba;
   221 	bool is_type1 = false;
   222 	int temp;
   223 	m68k_end_timeslice();
   225 	switch (addr) {
   226 		case WD2797_REG_COMMAND:	// Command register
   227 			// write to command register clears interrupt request
   228 			ctx->irq = false;
   230 			// Is the drive ready?
   231 			if (ctx->disc_image == NULL) {
   232 				// No disc image, thus the drive is busy.
   233 				ctx->status = 0x80;
   234 				return;
   235 			}
   237 			// Handle Type 1 commands
   238 			switch (cmd) {
   239 				case CMD_RESTORE:
   240 					// Restore. Set track to 0 and throw an IRQ.
   241 					is_type1 = true;
   242 					ctx->track = ctx->track_reg = 0;
   243 					break;
   245 				case CMD_SEEK:
   246 					// Seek. Seek to the track specced in the Data Register.
   247 					is_type1 = true;
   248 					if (ctx->data_reg < ctx->geom_tracks) {
   249 						ctx->track = ctx->track_reg = ctx->data_reg;
   250 					} else {
   251 						// Seek error. :(
   252 						ctx->status = 0x10;
   253 					}
   255 				case CMD_STEP:
   256 					// TODO! deal with trk0!
   257 					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   258 					is_type1 = true;
   259 					break;
   261 				case CMD_STEPIN:
   262 				case CMD_STEPOUT:
   263 				case CMD_STEP_TU:
   264 				case CMD_STEPIN_TU:
   265 				case CMD_STEPOUT_TU:
   266 					// if this is a Step In or Step Out cmd, set the step-direction
   267 					if ((cmd & ~0x10) == CMD_STEPIN) {
   268 						ctx->last_step_dir = 1;
   269 					} else if ((cmd & ~0x10) == CMD_STEPOUT) {
   270 						ctx->last_step_dir = -1;
   271 					}
   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 					if (cmd & 0x10){
   283 						ctx->track_reg = ctx->track;
   284 					}
   286 					is_type1 = true;
   287 					break;
   289 				default:
   290 					break;
   291 			}
   293 			if (is_type1) {
   294 				// Terminate any sector reads or writes
   295 				ctx->data_len = ctx->data_pos = 0;
   297 				// No DRQ bit for these commands.
   298 				ctx->cmd_has_drq = false;
   300 				// Type1 status byte...
   301 				ctx->status = 0;
   302 				// S7 = Not Ready. Command executed, therefore the drive was ready... :)
   303 				// S6 = Write Protect. TODO: add this
   304 				// S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
   305 				ctx->status |= 0x20;
   306 				// S4 = Seek Error. Not bloody likely if we got down here...!
   307 				// S3 = CRC Error. Not gonna happen on a disc image!
   308 				// S2 = Track 0
   309 				ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
   310 				// S1 = Index Pulse. TODO -- need periodics to emulate this
   311 				// S0 = Busy. We just exec'd the command, thus we're not busy.
   312 				// 		TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
   314 				// Set IRQ
   315 				ctx->irq = true;
   316 				return;
   317 			}
   319 			// That's the Type 1 (seek) commands sorted. Now for the others.
   321 			// All these commands return the DRQ bit...
   322 			ctx->cmd_has_drq = true;
   324 			// If drive isn't ready, then set status B7 and exit
   325 			if (ctx->disc_image == NULL) {
   326 				ctx->status = 0x80;
   327 				return;
   328 			}
   330 			// If this is a Write command, check write protect status too
   331 			if (!ctx->writeable) {
   332 				// Write protected disc...
   333 				if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
   334 					// Set Write Protect bit and bail.
   335 					ctx->status = 0x40;
   337 					// Set IRQ
   338 					ctx->irq = true;
   340 					return;
   341 				}
   342 			}
   344 			// Disc is ready to go. Parse the command word.
   345 			switch (cmd) {
   346 				case CMD_READ_ADDRESS:
   347 					// Read Address
   348 					ctx->head = (val & 0x02) ? 1 : 0;
   350 					// reset data pointers
   351 					ctx->data_pos = ctx->data_len = 0;
   353 					// load data buffer
   354 					ctx->data[ctx->data_len++] = ctx->track;
   355 					ctx->data[ctx->data_len++] = ctx->head;
   356 					ctx->data[ctx->data_len++] = ctx->sector;
   357 					switch (ctx->geom_secsz) {
   358 						case 128:	ctx->data[ctx->data_len++] = 0; break;
   359 						case 256:	ctx->data[ctx->data_len++] = 1; break;
   360 						case 512:	ctx->data[ctx->data_len++] = 2; break;
   361 						case 1024:	ctx->data[ctx->data_len++] = 3; break;
   362 						default:	ctx->data[ctx->data_len++] = 0xFF; break;	// TODO: deal with invalid values better
   363 					}
   364 					ctx->data[ctx->data_len++] = 0;	// TODO: IDAM CRC!
   365 					ctx->data[ctx->data_len++] = 0;
   367 					ctx->status = 0;
   368 					// B6, B5 = 0
   369 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   370 					// B3 = CRC Error. Not possible.
   371 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   372 					// B1 = DRQ. Data request.
   373 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   374 					break;
   376 				case CMD_READ_SECTOR:
   377 				case CMD_READ_SECTOR_MULTI:
   378 					ctx->head = (val & 0x02) ? 1 : 0;
   379 					LOG("WD279X: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   380 					// Read Sector or Read Sector Multiple
   382 					// Check to see if the cyl, hd and sec are valid
   383 					if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt) || (ctx->sector == 0)) {
   384 						LOG("*** WD2797 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   385 								ctx->track, ctx->head, ctx->sector,
   386 								ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
   387 						// CHS parameters exceed limits
   388 						ctx->status = 0x10;		// Record Not Found
   389 						// Set IRQ
   390 						ctx->irq = true;
   391 						break;
   392 					}
   394 					// reset data pointers
   395 					ctx->data_pos = ctx->data_len = 0;
   397 					// Calculate number of sectors to read from disc
   398 					if (cmd == CMD_READ_SECTOR_MULTI)
   399 						temp = ctx->geom_spt;
   400 					else
   401 						temp = 1;
   403 					for (int i=0; i<temp; i++) {
   404 						// Calculate the LBA address of the required sector
   405 						// LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
   406 						lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i) - 1;
   407 						// convert LBA to byte address
   408 						lba *= ctx->geom_secsz;
   409 						LOG("\tREAD lba = %lu", lba);
   411 						// Read the sector from the file
   412 						fseek(ctx->disc_image, lba, SEEK_SET);
   413 						// TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   414 						ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
   415 						LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
   416 					}
   418 					ctx->status = 0;
   419 					// B6 = 0
   420 					// B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
   421 					// B4 = Record Not Found. Basically, the CHS parameters are bullcrap.
   422 					// B3 = CRC Error. Not possible.
   423 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   424 					// B1 = DRQ. Data request.
   425 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   426 					break;
   428 				case CMD_READ_TRACK:
   429 					// Read Track
   430 					// TODO! implement this
   431 					// ctx->head = (val & 0x02) ? 1 : 0;
   432 					// ctx->status = 0;
   433 					// B6, B5, B4, B3 = 0
   434 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   435 					// B1 = DRQ. Data request.
   436 					// ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   437 					ctx->irq = true;
   438 					ctx->status = 0x10;
   439 					break;
   441 				case CMD_WRITE_SECTOR:
   442 				case CMD_WRITE_SECTOR_MULTI:
   443 					// Write Sector or Write Sector Multiple
   445 					ctx->head = (val & 0x02) ? 1 : 0;
   446 					// reset data pointers
   447 					ctx->data_pos = 0;
   449 					// Calculate number of sectors to write to disc
   450 					if (cmd == CMD_WRITE_SECTOR_MULTI)
   451 						/*XXX: is this the correct value?*/
   452 						temp = ctx->geom_spt;
   453 					else
   454 						temp = 1;
   455 					ctx->data_len = temp * ctx->geom_secsz;
   457 					lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector)) - 1;
   458 					ctx->write_pos = lba * ctx->geom_secsz;
   460 					ctx->status = 0;
   461 					// B6 = Write Protect. This would have been set earlier.
   462 					// B5 = 0
   463 					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   464 					// B3 = CRC Error. Not possible.
   465 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   466 					// B1 = DRQ. Data request.
   467 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   468 					break;
   470 				case CMD_FORMAT_TRACK:
   471 					// Write Track (aka Format Track)
   472 					ctx->head = (val & 0x02) ? 1 : 0;
   473 					ctx->status = 0;
   474 					// B6 = Write Protect. FIXME -- emulate this!
   475 					// B5, B4, B3 = 0
   476 					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   477 					ctx->data_pos = 0;
   478 					ctx->data_len = 7170;
   479 					// B1 = DRQ. Data request.
   480 					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   481 					ctx->formatting = true;
   482 					break;
   484 				case CMD_FORCE_INTERRUPT:
   485 					// Force Interrupt...
   486 					// Terminates current operation and sends an interrupt
   487 					// TODO!
   488 					ctx->status = 0x20;
   489 					if (!ctx->writeable){
   490 						ctx->status |= 0x40;
   491 					}
   492 					if (ctx->track == 0){
   493 						ctx->status = 0x04;
   494 					}
   495 					ctx->data_pos = ctx->data_len = 0;
   496 					if (cmd & 8){
   497 						// Set IRQ
   498 						ctx->irq = true;
   499 					}
   500 					break;
   501 			}
   502 			break;
   504 		case WD2797_REG_TRACK:		// Track register
   505 			ctx->track = ctx->track_reg = val;
   506 			break;
   508 		case WD2797_REG_SECTOR:		// Sector register
   509 			ctx->sector = val;
   510 			break;
   512 		case WD2797_REG_DATA:		// Data register
   513 			// Save the value written into the data register
   514 			ctx->data_reg = val;
   515 			// If we're processing a write command, and there's space in the
   516 			// buffer, allow the write.
   517 			if (ctx->data_pos < ctx->data_len && (ctx->write_pos >= 0 || ctx->formatting)) {
   518 				if (!ctx->formatting) ctx->data[ctx->data_pos] = val;
   519 				// store data byte and increment pointer
   520 				ctx->data_pos++;
   522 				// set IRQ and write data if this is the last data byte
   523 				if (ctx->data_pos == ctx->data_len) {
   524 					if (!ctx->formatting){
   525 						fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
   526 						fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
   527 						fflush(ctx->disc_image);
   528 					}
   529 					// Set IRQ and reset write pointer
   530 					ctx->irq = true;
   531 					ctx->write_pos = -1;
   532 					ctx->formatting = false;
   533 				}
   535 			}
   536 			break;
   537 	}
   538 }
   540 void wd2797_dma_miss(WD2797_CTX *ctx)
   541 {
   542 	ctx->data_pos = ctx->data_len;
   543 	ctx->write_pos = 0;
   544 	ctx->status = 4; /* lost data */
   545 	ctx->irq = true;
   546 }