src/wd2010.c

Wed, 13 Mar 2013 00:43:25 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Wed, 13 Mar 2013 00:43:25 +0000
changeset 134
b826697f411a
parent 130
1fc7d607dbb4
child 135
159f937af10d
child 136
f7d78dfb45d0
permissions
-rw-r--r--

[wd2010,main] WD2010 disc geometry fixes

I believe I have fixed the geometry problem with FreeBee. The geometry was set
to 17 sectors per track instead of 16, which obviously throws off addressing.
I changed it to use 16 sectors per track. However, s4diag tries to format
sector 17, so I changed the WD2010 emulation to accept any address when
formatting (since the format command doesn't actually do anything, it doesn't
matter). It is now possible to format the hard disk, initialize the file
system, and mount it. However, cpio still fails to copy the system to the hard
disk.

Author: Andrew Warkentin <andreww591 gmail com>

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