src/wd2010.c

Mon, 14 Jan 2013 09:48:21 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Mon, 14 Jan 2013 09:48:21 +0000
changeset 119
101fe02456ce
parent 116
21521e62007f
child 122
b214cf455ff2
permissions
-rw-r--r--

Handle memory more gracefully

Fixed in this cset:

* Return an 'empty space' value if the memory wraps around

* Allow the 'empty bus' value to be changed via an ifdef

* Properly handle situations where expansion memory is turned off

     1 #include <stdint.h>
     2 #include <stdbool.h>
     3 #include <malloc.h>
     4 #include "SDL.h"
     5 #include "musashi/m68k.h"
     6 #include "wd2010.h"
     8 #define WD2010_DEBUG
    10 #ifndef WD2010_DEBUG
    11 #define NDEBUG
    12 #endif
    13 #include "utils.h"
    15 #define SEEK_DELAY 30
    17 #define CMD_ENABLE_RETRY 0x01
    18 #define CMD_LONG_MODE 0x02
    19 #define CMD_MULTI_SECTOR 0x04
    20 #define CMD_INTRQ_WHEN_COMPLETE 0x08
    22 #define ER_BAD_BLOCK 0x80
    23 #define ER_CRC 0x40
    24 #define ER_ID_NOT_FOUND 0x10
    25 #define ER_ABORTED_COMMAND 0x04
    26 #define ER_NO_TK0 0x02
    27 #define ER_NO_ADDRESS_MARK 0x01
    29 #define SR_BUSY 0x80
    30 #define SR_READY 0x40
    31 #define SR_WRITE_FAULT 0x20
    32 #define SR_SEEK_COMPLETE 0x10
    33 #define SR_DRQ 0x08
    34 #define SR_CORRECTED 0x04
    35 #define SR_COMMAND_IN_PROGRESS 0x02
    36 #define SR_ERROR 0x01
    38 extern int cpu_log_enabled;
    40 /// WD2010 command constants
    41 enum {
    42 	CMD_MASK				= 0xF0,		///< Bit mask to detect command bits
    43 	CMD_2010_EXT			= 0x00,		///< WD2010 extended commands (compute correction, set parameter)
    44 	CMD_RESTORE				= 0x10,		///< Restore (recalibrate, seek to track 0)
    45 	CMD_READ_SECTOR			= 0x20,		///< Read sector
    46 	CMD_WRITE_SECTOR		= 0x30,		///< Write sector
    47 	CMD_SCAN_ID				= 0x40,		///< Scan ID
    48 	CMD_WRITE_FORMAT		= 0x50,		///< Write format
    49 	CMD_SEEK				= 0x70,		///< Seek to given track
    50 };
    52 int wd2010_init(WD2010_CTX *ctx, FILE *fp, int secsz, int spt, int heads)
    53 {
    54 	long filesize;
    55 	wd2010_reset(ctx);
    56 	// Start by finding out how big the image file is
    57 	fseek(fp, 0, SEEK_END);
    58 	filesize = ftell(fp);
    59 	fseek(fp, 0, SEEK_SET);
    61 	// Now figure out how many tracks it contains
    62 	int tracks = filesize / secsz / spt / heads;
    63 	// Confirm...
    64 	if (tracks < 1) {
    65 		return WD2010_ERR_BAD_GEOM;
    66 	}
    68 	// Allocate enough memory to store one disc track
    69 	if (ctx->data) {
    70 		free(ctx->data);
    71 	}
    72 	ctx->data = malloc(secsz * spt);
    73 	if (!ctx->data)
    74 		return WD2010_ERR_NO_MEMORY;
    76 	// Load the image and the geometry data
    77 	ctx->disc_image = fp;
    78 	ctx->geom_tracks = tracks;
    79 	ctx->geom_secsz = secsz;
    80 	ctx->geom_heads = heads;
    81 	ctx->geom_spt = spt;
    82 	return WD2010_ERR_OK;
    84 }
    86 void wd2010_reset(WD2010_CTX *ctx)
    87 {
    88 	// track, head and sector unknown
    89 	ctx->track = ctx->head = ctx->sector = 0;
    91 	// no IRQ pending
    92 	ctx->irq = false;
    94 	// no data available
    95 	ctx->data_pos = ctx->data_len = 0;
    97 	// Status register clear, not busy
    98 	ctx->status = 0;
   100 	ctx->sector_count = 0;
   101 	ctx->sector_number = 0;
   102 	ctx->cylinder_low_reg = 0;
   103 	ctx->cylinder_high_reg = 0;
   104 	ctx->sdh = 0;
   105 	ctx->mcr2_hdsel3 = 0;
   106 	ctx->mcr2_ddrive1 = 0;
   107 }
   109 void wd2010_done(WD2010_CTX *ctx)
   110 {
   111 	// Reset the WD2010
   112 	wd2010_reset(ctx);
   114 	// Free any allocated memory
   115 	if (ctx->data) {
   116 		free(ctx->data);
   117 		ctx->data = NULL;
   118 	}
   119 }
   122 bool wd2010_get_irq(WD2010_CTX *ctx)
   123 {
   124 	return ctx->irq;
   125 }
   127 bool wd2010_get_drq(WD2010_CTX *ctx)
   128 {
   129 	return (ctx->drq && ctx->data_pos < ctx->data_len);
   130 }
   132 void wd2010_dma_miss(WD2010_CTX *ctx)
   133 {
   134 	ctx->data_pos = ctx->data_len;
   135 	ctx->write_pos = 0;
   136 	ctx->status = SR_READY | SR_SEEK_COMPLETE;
   137 	ctx->irq = true;
   138 }
   140 uint8_t wd2010_read_data(WD2010_CTX *ctx)
   141 {
   142 	// If there's data in the buffer, return it. Otherwise return 0xFF.
   143 	if (ctx->data_pos < ctx->data_len) {
   144 		if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
   145 			ctx->sector_count--;
   146 			ctx->sector_number++;
   147 		}
   148 		// set IRQ if this is the last data byte
   149 		if (ctx->data_pos == (ctx->data_len-1)) {
   150 			ctx->status = SR_READY | SR_SEEK_COMPLETE;
   151 			// Set IRQ
   152 			ctx->irq = true;
   153 			ctx->drq = false;
   154 		}
   155 		// return data byte and increment pointer
   156 		return ctx->data[ctx->data_pos++];
   157 	} else {
   158 		// empty buffer (this shouldn't happen)
   159 		LOGS("WD2010: attempt to read from empty data buffer");
   160 		return 0xff;
   161 	}
   162 }
   164 void wd2010_write_data(WD2010_CTX *ctx, uint8_t val)
   165 {
   166 	// If we're processing a write command, and there's space in the
   167 	// buffer, allow the write.
   168 	if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) {
   169 		// store data byte and increment pointer
   170 		if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
   171 			ctx->sector_count--;
   172 			ctx->sector_number++;
   173 		}
   174 		ctx->data[ctx->data_pos++] = val;
   175 		// set IRQ and write data if this is the last data byte
   176 		if (ctx->data_pos == ctx->data_len) {
   177 			if (!ctx->formatting){
   178 				fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
   179 				fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
   180 				fflush(ctx->disc_image);
   181 			}
   182 			ctx->formatting = false;
   183 			ctx->status = SR_READY | SR_SEEK_COMPLETE;
   184 			// Set IRQ and reset write pointer
   185 			ctx->irq = true;
   186 			ctx->write_pos = -1;
   187 			ctx->drq = false;
   188 		}
   189 	}else{
   190 		LOGS("WD2010: attempt to write to data buffer without a write command in progress");
   191 	}
   192 }
   194 uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx)
   195 {
   196 	/*m68k_end_timeslice();*/
   197 	ctx->status = SR_READY | SR_SEEK_COMPLETE;
   198 	ctx->irq = true;
   199 	return (0);
   200 }
   202 uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx)
   203 {
   204 	/*m68k_end_timeslice();*/
   205 	ctx->drq = true;
   206 	return (0);
   207 }
   209 uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr)
   210 {
   211 	uint8_t temp = 0;
   213 	/*cpu_log_enabled = 1;*/
   215 	switch (addr & 0x07) {
   216 		case WD2010_REG_ERROR:
   217 			return ctx->error_reg;
   218 		case WD2010_REG_SECTOR_COUNT:
   219 			return ctx->sector_count;
   220 		case WD2010_REG_SECTOR_NUMBER:
   221 			return ctx->sector_number;
   222 		case WD2010_REG_CYLINDER_HIGH:      // High byte of cylinder
   223 			return ctx->cylinder_high_reg;
   224 		case WD2010_REG_CYLINDER_LOW:       // Low byte of cylinder
   225 			return ctx->cylinder_low_reg;
   226 		case WD2010_REG_SDH:
   227 			return ctx->sdh;
   228 		case WD2010_REG_STATUS:             // Status register
   229 			// Read from status register clears IRQ
   230 			ctx->irq = false;
   231 			// Get current status flags (set by last command)
   232 			// DRQ bit
   233 			if (ctx->cmd_has_drq) {
   234 				temp = ctx->status & ~(SR_BUSY & SR_DRQ);
   235 				temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0;
   236 				LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
   237 			} else {
   238 				temp = ctx->status & ~0x80;
   239 			}
   240 			/*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/
   241 			// HDC is busy if there is still data in the buffer
   242 			temp |= (ctx->data_pos < ctx->data_len) ? SR_BUSY : 0;	// if data in buffer, then DMA hasn't copied it yet, and we're still busy!
   243 																	// TODO: also if seek delay / read delay hasn't passed (but that's for later)
   244 			/*XXX: should anything else be set here?*/
   245 			return temp;
   246 		default:
   247 			// shut up annoying compilers which don't recognise unreachable code when they see it
   248 			// (here's looking at you, gcc!)
   249 			return 0xff;
   250 	}
   251 }
   254 void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val)
   255 {
   256 	uint8_t cmd = val & CMD_MASK;
   257 	size_t lba;
   258 	int new_track;
   259 	int sector_count;
   261 	m68k_end_timeslice();
   263 	/*cpu_log_enabled = 1;*/
   265 	if (addr == UNIXPC_REG_MCR2) {
   266 		// The UNIX PC has an "MCR2" register with the following format:
   267 		//   [ 7..2 ][1][0]
   268 		//   Bits 7..2: Not used
   269 		//   Bit 1:     DDRIVE1 (hard disk drive 1 select - not used?)
   270 		//   Bit 0:     HDSEL3  (head-select bit 3)
   271 		ctx->mcr2_hdsel3 = ((val & 1) == 1);
   272 		ctx->mcr2_ddrive1 = ((val & 2) == 2);
   273 		return;
   274 	}
   276 	switch (addr & 0x07) {
   277 		case WD2010_REG_WRITE_PRECOMP_CYLINDER:
   278 			break;
   279 		case WD2010_REG_SECTOR_COUNT:
   280 			ctx->sector_count = val;
   281 			break;
   282 		case WD2010_REG_SECTOR_NUMBER:
   283 			ctx->sector_number = val;
   284 			break;
   285 		case WD2010_REG_CYLINDER_HIGH:		// High byte of cylinder
   286 			ctx->cylinder_high_reg = val;
   287 			break;
   288 		case WD2010_REG_CYLINDER_LOW:		// Low byte of cylinder
   289 			ctx->cylinder_low_reg = val;
   290 			break;
   291 		case WD2010_REG_SDH:
   292 			/*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/
   293 			ctx->data_pos = ctx->data_len = 0;
   294 			ctx->sdh = val;
   295 			break;
   296 		case WD2010_REG_COMMAND:	// Command register
   297 			// write to command register clears interrupt request
   298 			ctx->irq = false;
   299 			ctx->error_reg = 0;
   301 			/*cpu_log_enabled = 1;*/
   302 			switch (cmd) {
   303 				case CMD_RESTORE:
   304 					// Restore. Set track to 0 and throw an IRQ.
   305 					ctx->track = 0;
   306 					SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
   307 					break;
   308 				case CMD_SCAN_ID:
   309 					ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff;
   310 					ctx->cylinder_low_reg = ctx->track & 0xff;
   311 					ctx->sector_number = ctx->sector;
   312 					ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7);
   313 				case CMD_WRITE_FORMAT:
   314 				case CMD_SEEK:
   315 				case CMD_READ_SECTOR:
   316 				case CMD_WRITE_SECTOR:
   317 					// Seek. Seek to the track specced in the cylinder
   318 					// registers.
   319 					new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg;
   320 					if (new_track < ctx->geom_tracks) {
   321 						ctx->track = new_track;
   322 					} else {
   323 						// Seek error. :(
   324 						ctx->status = SR_ERROR;
   325 						ctx->error_reg = ER_ID_NOT_FOUND;
   326 						ctx->irq = true;
   327 						break;
   328 					}
   329 					// The SDH register provides 3 head select bits; the 4th comes from MCR2.
   330 					ctx->head = (ctx->sdh & 0x07) + (ctx->mcr2_hdsel3 ? 8 : 0);
   331 					ctx->sector = ctx->sector_number;
   333 					ctx->formatting = cmd == CMD_WRITE_FORMAT;
   334 					switch (cmd){
   335 						case CMD_SEEK:
   336 							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
   337 							break;
   338 						case CMD_READ_SECTOR:
   339 							/*XXX: does a separate function to set the head have to be added?*/
   340 							LOG("WD2010: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   342 							// Read Sector
   344 							// Check to see if the cyl, hd and sec are valid
   345 							if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt - 1)) {
   346 								LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   347 										ctx->track, ctx->head, ctx->sector,
   348 										ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt-1);
   349 								// CHS parameters exceed limits
   350 								ctx->status = SR_ERROR;
   351 								ctx->error_reg = ER_ID_NOT_FOUND;
   352 								// Set IRQ
   353 								ctx->irq = true;
   354 								break;
   355 							}
   357 							// reset data pointers
   358 							ctx->data_pos = ctx->data_len = 0;
   360 							if (val & CMD_MULTI_SECTOR){
   361 								ctx->multi_sector = 1;
   362 								sector_count = ctx->sector_count;
   363 							}else{
   364 								ctx->multi_sector = 0;
   365 								sector_count = 1;
   366 							}
   367 							for (int i=0; i<sector_count; i++) {
   368 								// Calculate the LBA address of the required sector
   369 								// LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
   370 								lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i);
   371 								// convert LBA to byte address
   372 								lba *= ctx->geom_secsz;
   373 								LOG("\tREAD lba = %lu", lba);
   375 								// Read the sector from the file
   376 								fseek(ctx->disc_image, lba, SEEK_SET);
   377 								// TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   378 								ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
   379 								LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
   380 							}
   382 							ctx->status = 0;
   383 							ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
   384 							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
   386 							break;
   387 						case CMD_WRITE_FORMAT:
   388 							ctx->sector = 0;
   389 						case CMD_WRITE_SECTOR:
   390 							LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   391 							// Read Sector
   393 							// Check to see if the cyl, hd and sec are valid
   394 							if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt-1)) {
   395 								LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   396 										ctx->track, ctx->head, ctx->sector,
   397 										ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
   398 								// CHS parameters exceed limits
   399 								ctx->status = SR_ERROR;
   400 								ctx->error_reg = ER_ID_NOT_FOUND;
   401 								// Set IRQ
   402 								ctx->irq = true;
   403 								break;
   404 							}
   406 							// reset data pointers
   407 							ctx->data_pos = ctx->data_len = 0;
   409 							if (val & CMD_MULTI_SECTOR){
   410 								ctx->multi_sector = 1;
   411 								sector_count = ctx->sector_count;
   412 							}else{
   413 								ctx->multi_sector = 0;
   414 								sector_count = 1;
   415 							}
   416 							ctx->data_len = ctx->geom_secsz * sector_count;
   417 							lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector));
   418 							// convert LBA to byte address
   419 							ctx->write_pos = lba * ctx->geom_secsz;
   420 							LOG("\tWRITE lba = %lu", lba);
   422 							ctx->status = 0;
   423 							ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
   424 							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
   426 							break;
   427 						default:
   428 							LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd);
   429 							break;
   430 					}
   431 					break;
   432 				case CMD_2010_EXT: /* not implemented */
   433 				default:
   434 					LOG("WD2010: unknown command %x\n", cmd);
   435 					ctx->status = SR_ERROR;
   436 					ctx->error_reg = ER_ABORTED_COMMAND;
   437 					ctx->irq = true;
   438 					break;
   439 			}
   440 			break;
   442 	}
   443 }