src/wd2010.c

Sat, 17 Nov 2012 19:18:29 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Sat, 17 Nov 2012 19:18:29 +0000
changeset 112
a392eb8f9806
child 115
da3d10af0711
permissions
-rw-r--r--

add HDD support + fixes

Patch-Author: Andrew Warkentin <andreww591!gmail>
Patch-Message-ID: <50A772FC.8020009@gmail.com>

I have added floppy write support, full hard disk emulation, and proper handling of DMA page faults to FreeBee. I also fixed the floppy step commands, changed the "force interrupt" floppy command to generate a type 1 status, and changed the DMA address counter to reset to 3fff when a transfer completes (which is what Unix seems to expect - without it, the kernel says that the floppy isn't ready). The floppy, hard disk, and DMA page fault tests all pass. Initializing hard disks and floppies also works (the geometry for both is still fixed by the size of the image, though, and format commands only appear to succeed, but they don't modify the image). Unix still doesn't run, though (it hangs after reading some sectors from the floppy).

     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 }
   107 void wd2010_done(WD2010_CTX *ctx)
   108 {
   109 	// Reset the WD2010
   110 	wd2010_reset(ctx);
   112 	// Free any allocated memory
   113 	if (ctx->data) {
   114 		free(ctx->data);
   115 		ctx->data = NULL;
   116 	}
   117 }
   120 bool wd2010_get_irq(WD2010_CTX *ctx)
   121 {
   122 	return ctx->irq;
   123 }
   125 bool wd2010_get_drq(WD2010_CTX *ctx)
   126 {
   127 	return (ctx->drq && ctx->data_pos < ctx->data_len);
   128 }
   130 void wd2010_dma_miss(WD2010_CTX *ctx)
   131 {
   132 	ctx->data_pos = ctx->data_len;
   133 	ctx->write_pos = 0;
   134 	ctx->status = SR_READY | SR_SEEK_COMPLETE;
   135 	ctx->irq = true;
   136 }
   138 uint8_t wd2010_read_data(WD2010_CTX *ctx)
   139 {
   140 	// If there's data in the buffer, return it. Otherwise return 0xFF.
   141 	if (ctx->data_pos < ctx->data_len) {
   142 		if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
   143 			ctx->sector_count--;
   144 			ctx->sector_number++;
   145 		}
   146 		// set IRQ if this is the last data byte
   147 		if (ctx->data_pos == (ctx->data_len-1)) {
   148 			ctx->status = SR_READY | SR_SEEK_COMPLETE;
   149 			// Set IRQ
   150 			ctx->irq = true;
   151 			ctx->drq = false;
   152 		}
   153 		// return data byte and increment pointer
   154 		return ctx->data[ctx->data_pos++];
   155 	} else {
   156 		// empty buffer (this shouldn't happen)
   157 		LOG("WD2010: attempt to read from empty data buffer");
   158 		return 0xff;
   159 	}
   160 }
   162 void wd2010_write_data(WD2010_CTX *ctx, uint8_t val)
   163 {
   164 	// If we're processing a write command, and there's space in the
   165 	// buffer, allow the write.
   166 	if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) {
   167 		// store data byte and increment pointer
   168 		if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
   169 			ctx->sector_count--;
   170 			ctx->sector_number++;
   171 		}
   172 		ctx->data[ctx->data_pos++] = val;
   173 		// set IRQ and write data if this is the last data byte
   174 		if (ctx->data_pos == ctx->data_len) {
   175 			if (!ctx->formatting){
   176 				fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
   177 				fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
   178 				fflush(ctx->disc_image);
   179 			}
   180 			ctx->formatting = false;
   181 			ctx->status = SR_READY | SR_SEEK_COMPLETE;
   182 			// Set IRQ and reset write pointer
   183 			ctx->irq = true;
   184 			ctx->write_pos = -1;
   185 			ctx->drq = false;
   186 		}
   187 	}else{
   188 		LOG("WD2010: attempt to write to data buffer without a write command in progress");
   189 	}
   190 }
   192 uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx)
   193 {
   194 	/*m68k_end_timeslice();*/
   195 	ctx->status = SR_READY | SR_SEEK_COMPLETE;
   196 	ctx->irq = true;
   197 	return (0);
   198 }
   200 uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx)
   201 {
   202 	/*m68k_end_timeslice();*/
   203 	ctx->drq = true;
   204 	return (0);
   205 }
   207 uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr)
   208 {
   209 	uint8_t temp = 0;
   211 	/*cpu_log_enabled = 1;*/
   213 	switch (addr & 0x07) {
   214 		case WD2010_REG_ERROR:
   215 			return ctx->error_reg;
   216 		case WD2010_REG_SECTOR_COUNT:
   217 			return ctx->sector_count;
   218 		case WD2010_REG_SECTOR_NUMBER:
   219 			return ctx->sector_number;
   220 		case WD2010_REG_CYLINDER_HIGH:      // High byte of cylinder
   221 			return ctx->cylinder_high_reg;
   222 		case WD2010_REG_CYLINDER_LOW:       // Low byte of cylinder
   223 			return ctx->cylinder_low_reg;
   224 		case WD2010_REG_SDH:
   225 			return ctx->sdh;
   226 		case WD2010_REG_STATUS:             // Status register
   227 			// Read from status register clears IRQ
   228 			ctx->irq = false;
   229 			// Get current status flags (set by last command)
   230 			// DRQ bit
   231 			if (ctx->cmd_has_drq) {
   232 				temp = ctx->status & ~(SR_BUSY & SR_DRQ);
   233 				temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0;
   234 				LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
   235 			} else {
   236 				temp = ctx->status & ~0x80;
   237 			}
   238 			/*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/
   239 			// HDC is busy if there is still data in the buffer
   240 			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!
   241 																	// TODO: also if seek delay / read delay hasn't passed (but that's for later)
   242 			/*XXX: should anything else be set here?*/
   243 			return temp;
   244 		default:
   245 			// shut up annoying compilers which don't recognise unreachable code when they see it
   246 			// (here's looking at you, gcc!)
   247 			return 0xff;
   248 	}
   249 }
   252 void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val)
   253 {
   254 	uint8_t cmd = val & CMD_MASK;
   255 	size_t lba;
   256 	int new_track;
   257 	int sector_count;
   259 	m68k_end_timeslice();
   261 	/*cpu_log_enabled = 1;*/
   263 	switch (addr & 0x07) {
   264 		case WD2010_REG_WRITE_PRECOMP_CYLINDER:
   265 			break;
   266 		case WD2010_REG_SECTOR_COUNT:
   267 			ctx->sector_count = val;
   268 			break;
   269 		case WD2010_REG_SECTOR_NUMBER:
   270 			ctx->sector_number = val;
   271 			break;
   272 		case WD2010_REG_CYLINDER_HIGH:		// High byte of cylinder
   273 			ctx->cylinder_high_reg = val;
   274 			break;
   275 		case WD2010_REG_CYLINDER_LOW:		// Low byte of cylinder
   276 			ctx->cylinder_low_reg = val;
   277 			break;
   278 		case WD2010_REG_SDH:
   279 			/*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/
   280 			ctx->data_pos = ctx->data_len = 0;
   281 			ctx->sdh = val;
   282 			break;
   283 		case WD2010_REG_COMMAND:	// Command register
   284 			// write to command register clears interrupt request
   285 			ctx->irq = false;
   286 			ctx->error_reg = 0;
   288 			/*cpu_log_enabled = 1;*/
   289 			switch (cmd) {
   290 				case CMD_RESTORE:
   291 					// Restore. Set track to 0 and throw an IRQ.
   292 					ctx->track = 0;
   293 					SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
   294 					break;
   295 				case CMD_SCAN_ID:
   296 					ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff;
   297 					ctx->cylinder_low_reg = ctx->track & 0xff;
   298 					ctx->sector_number = ctx->sector;
   299 					ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7);
   300 				case CMD_WRITE_FORMAT:
   301 				case CMD_SEEK:
   302 				case CMD_READ_SECTOR:
   303 				case CMD_WRITE_SECTOR:
   304 					// Seek. Seek to the track specced in the cylinder
   305 					// registers.
   306 					new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg;
   307 					if (new_track < ctx->geom_tracks) {
   308 						ctx->track = new_track;
   309 					} else {
   310 						// Seek error. :(
   311 						ctx->status = SR_ERROR;
   312 						ctx->error_reg = ER_ID_NOT_FOUND;
   313 						ctx->irq = true;
   314 						break;
   315 					}
   316 					ctx->head = ctx->sdh & 0x07;
   317 					ctx->sector = ctx->sector_number;
   319 					ctx->formatting = cmd == CMD_WRITE_FORMAT;
   320 					switch (cmd){
   321 						case CMD_SEEK:
   322 							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
   323 							break;
   324 						case CMD_READ_SECTOR:
   325 							/*XXX: does a separate function to set the head have to be added?*/
   326 							LOG("WD2010: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   328 							// Read Sector
   330 							// Check to see if the cyl, hd and sec are valid
   331 							if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt - 1)) {
   332 								LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   333 										ctx->track, ctx->head, ctx->sector,
   334 										ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt-1);
   335 								// CHS parameters exceed limits
   336 								ctx->status = SR_ERROR;
   337 								ctx->error_reg = ER_ID_NOT_FOUND;
   338 								// Set IRQ
   339 								ctx->irq = true;
   340 								break;
   341 							}
   343 							// reset data pointers
   344 							ctx->data_pos = ctx->data_len = 0;
   346 							if (val & CMD_MULTI_SECTOR){
   347 								ctx->multi_sector = 1;
   348 								sector_count = ctx->sector_count;
   349 							}else{
   350 								ctx->multi_sector = 0;
   351 								sector_count = 1;
   352 							}
   353 							for (int i=0; i<sector_count; i++) {
   354 								// Calculate the LBA address of the required sector
   355 								// LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
   356 								lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i);
   357 								// convert LBA to byte address
   358 								lba *= ctx->geom_secsz;
   359 								LOG("\tREAD lba = %lu", lba);
   361 								// Read the sector from the file
   362 								fseek(ctx->disc_image, lba, SEEK_SET);
   363 								// TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   364 								ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
   365 								LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
   366 							}
   368 							ctx->status = 0;
   369 							ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
   370 							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
   372 							break;
   373 						case CMD_WRITE_FORMAT:
   374 							ctx->sector = 0;
   375 						case CMD_WRITE_SECTOR:
   376 							LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   377 							// Read Sector
   379 							// Check to see if the cyl, hd and sec are valid
   380 							if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt-1)) {
   381 								LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   382 										ctx->track, ctx->head, ctx->sector,
   383 										ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
   384 								// CHS parameters exceed limits
   385 								ctx->status = SR_ERROR;
   386 								ctx->error_reg = ER_ID_NOT_FOUND;
   387 								// Set IRQ
   388 								ctx->irq = true;
   389 								break;
   390 							}
   392 							// reset data pointers
   393 							ctx->data_pos = ctx->data_len = 0;
   395 							if (val & CMD_MULTI_SECTOR){
   396 								ctx->multi_sector = 1;
   397 								sector_count = ctx->sector_count;
   398 							}else{
   399 								ctx->multi_sector = 0;
   400 								sector_count = 1;
   401 							}
   402 							ctx->data_len = ctx->geom_secsz * sector_count;
   403 							lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector));
   404 							// convert LBA to byte address
   405 							ctx->write_pos = lba * ctx->geom_secsz;
   406 							LOG("\tWRITE lba = %lu", lba);
   408 							ctx->status = 0;
   409 							ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
   410 							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
   412 							break;
   413 						default:
   414 							LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd);
   415 							break;
   416 					}
   417 					break;
   418 				case CMD_2010_EXT: /* not implemented */
   419 				default:
   420 					LOG("WD2010: unknown command %x\n", cmd);
   421 					ctx->status = SR_ERROR;
   422 					ctx->error_reg = ER_ABORTED_COMMAND;
   423 					ctx->irq = true;
   424 					break;
   425 			}
   426 			break;
   428 	}
   429 }