src/wd2010.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 116
21521e62007f
child 122
b214cf455ff2
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 "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 }