add HDD support + fixes

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
parent 111
4c85846b09cd
child 113
d3bb6a6a04b7

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).

Makefile file | annotate | diff | revisions
src/main.c file | annotate | diff | revisions
src/memory.c file | annotate | diff | revisions
src/memory.h file | annotate | diff | revisions
src/state.c file | annotate | diff | revisions
src/state.h file | annotate | diff | revisions
src/wd2010.c file | annotate | diff | revisions
src/wd2010.h file | annotate | diff | revisions
     1.1 --- a/Makefile	Sat Nov 17 19:13:08 2012 +0000
     1.2 +++ b/Makefile	Sat Nov 17 19:18:29 2012 +0000
     1.3 @@ -118,7 +118,7 @@
     1.4  TARGET		=	freebee
     1.5  
     1.6  # source files that produce object files
     1.7 -SRC			=	main.c state.c memory.c wd279x.c keyboard.c
     1.8 +SRC			=	main.c state.c memory.c wd279x.c wd2010.c keyboard.c
     1.9  SRC			+=	musashi/m68kcpu.c musashi/m68kdasm.c musashi/m68kops.c musashi/m68kopac.c musashi/m68kopdm.c musashi/m68kopnz.c
    1.10  
    1.11  # source type - either "c" or "cpp" (C or C++)
     2.1 --- a/src/main.c	Sat Nov 17 19:13:08 2012 +0000
     2.2 +++ b/src/main.c	Sat Nov 17 19:18:29 2012 +0000
     2.3 @@ -39,6 +39,23 @@
     2.4  	}
     2.5  }
     2.6  
     2.7 +static int load_hd()
     2.8 +{
     2.9 +
    2.10 +	state.hdc_disc0 = fopen("hd.img", "r+b");
    2.11 +	if (!state.hdc_disc0){
    2.12 +		fprintf(stderr, "ERROR loading disc image 'hd.img'.\n");
    2.13 +		state.hdc_disc0 = NULL;
    2.14 +		return (0);
    2.15 +	}else{
    2.16 +		wd2010_init(&state.hdc_ctx, state.hdc_disc0, 512, 16, 8);
    2.17 +		fprintf(stderr, "Disc image loaded.\n");
    2.18 +		return (1);
    2.19 +	}
    2.20 +}
    2.21 +
    2.22 +
    2.23 +
    2.24  /**
    2.25   * @brief Set the pixel at (x, y) to the given value
    2.26   * @note The surface must be locked before calling this!
    2.27 @@ -229,6 +246,8 @@
    2.28  	// Load a disc image
    2.29  	load_fd();
    2.30  
    2.31 +	load_hd();
    2.32 +
    2.33  	/***
    2.34  	 * The 3B1 CPU runs at 10MHz, with DMA running at 1MHz and video refreshing at
    2.35  	 * around 60Hz (???), with a 60Hz periodic interrupt.
    2.36 @@ -240,7 +259,8 @@
    2.37  	uint32_t next_timeslot = SDL_GetTicks() + MILLISECS_PER_TIMESLOT;
    2.38  	uint32_t clock_cycles = 0, tmp;
    2.39  	bool exitEmu = false;
    2.40 -	bool lastirq_fdc = false;
    2.41 +
    2.42 +	/*bool lastirq_fdc = false;*/
    2.43  	for (;;) {
    2.44  		// Run the CPU for however many cycles we need to. CPU core clock is
    2.45  		// 10MHz, and we're running at 240Hz/timeslot. Thus: 10e6/240 or
    2.46 @@ -254,77 +274,48 @@
    2.47  			size_t num = 0;
    2.48  			while (state.dma_count < 0x4000) {
    2.49  				uint16_t d = 0;
    2.50 -
    2.51  				// num tells us how many words we've copied. If this is greater than the per-timeslot DMA maximum, bail out!
    2.52  				if (num > (1e6/TIMESLOT_FREQUENCY)) break;
    2.53  
    2.54  				// Evidently we have more words to copy. Copy them.
    2.55 -				if (!wd2797_get_drq(&state.fdc_ctx)) {
    2.56 -					// Bail out, no data available. Try again later.
    2.57 -					// TODO: handle HDD controller too
    2.58 +				if (state.fd_selected){
    2.59 +					if (!wd2797_get_drq(&state.fdc_ctx)) {
    2.60 +						// Bail out, no data available. Try again later.
    2.61 +						break;
    2.62 +					}
    2.63 +				}else if (state.hd_selected){
    2.64 +					if (!wd2010_get_drq(&state.hdc_ctx)) {
    2.65 +						// Bail out, no data available. Try again later.
    2.66 +						break;
    2.67 +					}
    2.68 +				}else{
    2.69 +					printf("ERROR: DMA attempt with no drive selected!\n");
    2.70 +				}
    2.71 +				if (!access_check_dma(state.dma_reading)) {
    2.72  					break;
    2.73  				}
    2.74 -
    2.75 -				// Check memory access permissions
    2.76 -				// TODO: enforce these!!!! use ACCESS_CHECK_* for guidance.
    2.77 -				bool access_ok;
    2.78 -				switch (checkMemoryAccess(state.dma_address, !state.dma_reading)) {
    2.79 -					case MEM_PAGEFAULT:
    2.80 -						// Page fault
    2.81 -						state.genstat = 0x8BFF
    2.82 -							| (state.dma_reading ? 0x4000 : 0)
    2.83 -							| (state.pie ? 0x0400 : 0);
    2.84 -						access_ok = false;
    2.85 -						break;
    2.86 -
    2.87 -					case MEM_UIE:
    2.88 -						// User access to memory above 4MB
    2.89 -						// FIXME? Shouldn't be possible with DMA... assert this?
    2.90 -						state.genstat = 0x9AFF
    2.91 -							| (state.dma_reading ? 0x4000 : 0)
    2.92 -							| (state.pie ? 0x0400 : 0);
    2.93 -						access_ok = false;
    2.94 -						break;
    2.95 -
    2.96 -					case MEM_KERNEL:
    2.97 -					case MEM_PAGE_NO_WE:
    2.98 -						// Kernel access or page not write enabled
    2.99 -						access_ok = false;
   2.100 -						break;
   2.101 -
   2.102 -					case MEM_ALLOWED:
   2.103 -						access_ok = true;
   2.104 -						break;
   2.105 -				}
   2.106 -				if (!access_ok) {
   2.107 -					state.bsr0 = 0x3C00;
   2.108 -					state.bsr0 |= (state.dma_address >> 16);
   2.109 -					state.bsr1 = state.dma_address & 0xffff;
   2.110 -					if (state.ee) m68k_pulse_bus_error();
   2.111 -					printf("BUS ERROR FROM DMA: genstat=%04X, bsr0=%04X, bsr1=%04X\n", state.genstat, state.bsr0, state.bsr1);
   2.112 -
   2.113 -					// TODO: FIXME: if we get a pagefault, it NEEDS to be tagged as 'peripheral sourced'... this is a HACK!
   2.114 -					printf("REALLY BIG FSCKING HUGE ERROR: DMA Memory Access caused a FAULT!\n");
   2.115 -					exit(-1);
   2.116 -				}
   2.117 -
   2.118 +				uint32_t newAddr;
   2.119  				// Map logical address to a physical RAM address
   2.120 -				uint32_t newAddr = mapAddr(state.dma_address, !state.dma_reading);
   2.121 +				newAddr = mapAddr(state.dma_address, !state.dma_reading);
   2.122  
   2.123  				if (!state.dma_reading) {
   2.124 -					// Data available. Get it from the FDC. TODO: handle HDD too
   2.125 -					d = wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA);
   2.126 -					d <<= 8;
   2.127 -					d += wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA);
   2.128 -
   2.129 +					// Data available. Get it from the FDC or HDC.
   2.130 +					if (state.fd_selected) {
   2.131 +						d = wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA);
   2.132 +						d <<= 8;
   2.133 +						d += wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA);
   2.134 +					}else if (state.hd_selected) {
   2.135 +						d = wd2010_read_data(&state.hdc_ctx);
   2.136 +						d <<= 8;
   2.137 +						d += wd2010_read_data(&state.hdc_ctx);
   2.138 +					}
   2.139  					if (newAddr <= 0x1FFFFF) {
   2.140  						WR16(state.base_ram, newAddr, state.base_ram_size - 1, d);
   2.141  					} else if (newAddr >= 0x200000) {
   2.142  						WR16(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1, d);
   2.143  					}
   2.144 -					m68k_write_memory_16(state.dma_address, d);
   2.145  				} else {
   2.146 -					// Data write to FDC. TODO: handle HDD too.
   2.147 +					// Data write to FDC or HDC.
   2.148  
   2.149  					// Get the data from RAM
   2.150  					if (newAddr <= 0x1fffff) {
   2.151 @@ -336,9 +327,14 @@
   2.152  							d = 0xffff;
   2.153  					}
   2.154  
   2.155 -					// Send the data to the FDD
   2.156 -					wd2797_write_reg(&state.fdc_ctx, WD2797_REG_DATA, (d >> 8));
   2.157 -					wd2797_write_reg(&state.fdc_ctx, WD2797_REG_DATA, (d & 0xff));
   2.158 +					// Send the data to the FDD or HDD
   2.159 +					if (state.fd_selected){
   2.160 +						wd2797_write_reg(&state.fdc_ctx, WD2797_REG_DATA, (d >> 8));
   2.161 +						wd2797_write_reg(&state.fdc_ctx, WD2797_REG_DATA, (d & 0xff));
   2.162 +					}else if (state.hd_selected){
   2.163 +						wd2010_write_data(&state.hdc_ctx, (d >> 8));
   2.164 +						wd2010_write_data(&state.hdc_ctx, (d & 0xff));
   2.165 +					}
   2.166  				}
   2.167  
   2.168  				// Increment DMA address
   2.169 @@ -350,9 +346,11 @@
   2.170  			// Turn off DMA engine if we finished this cycle
   2.171  			if (state.dma_count >= 0x4000) {
   2.172  				// FIXME? apparently this isn't required... or is it?
   2.173 -//				state.dma_count = 0;
   2.174 -				state.dmaen = false;
   2.175 +				state.dma_count = 0x3fff;
   2.176 +				/*state.dmaen = false;*/
   2.177  			}
   2.178 +		}else if (wd2010_get_drq(&state.hdc_ctx)){
   2.179 +			wd2010_dma_miss(&state.hdc_ctx);
   2.180  		}else if (wd2797_get_drq(&state.fdc_ctx)){
   2.181  			wd2797_dma_miss(&state.fdc_ctx);
   2.182  		}
   2.183 @@ -364,8 +362,9 @@
   2.184  				lastirq_fdc = true;
   2.185  				m68k_set_irq(2);
   2.186  			}
   2.187 +		}
   2.188  */
   2.189 -		if (wd2797_get_irq(&state.fdc_ctx)){
   2.190 +		if (wd2797_get_irq(&state.fdc_ctx) || wd2010_get_irq(&state.hdc_ctx)) {
   2.191  			m68k_set_irq(2);
   2.192  		}else if (keyboard_get_irq(&state.kbd)) {
   2.193  			m68k_set_irq(3);
     3.1 --- a/src/memory.c	Sat Nov 17 19:13:08 2012 +0000
     3.2 +++ b/src/memory.c	Sat Nov 17 19:18:29 2012 +0000
     3.3 @@ -144,7 +144,8 @@
     3.4  			case MEM_KERNEL:										\
     3.5  			case MEM_PAGE_NO_WE:									\
     3.6  				/* kernel access or page not write enabled */		\
     3.7 -				/* FIXME: which regs need setting? */				\
     3.8 +				/* XXX: is this the correct value? */				\
     3.9 +				state.genstat = 0x9BFF | (state.pie ? 0x0400 : 0);	\
    3.10  				fault = true;										\
    3.11  				break;												\
    3.12  		}															\
    3.13 @@ -194,7 +195,8 @@
    3.14  			case MEM_KERNEL:										\
    3.15  			case MEM_PAGE_NO_WE:									\
    3.16  				/* kernel access or page not write enabled */		\
    3.17 -				/* FIXME: which regs need setting? */				\
    3.18 +				/* XXX: is this the correct value? */				\
    3.19 +				state.genstat = 0xDBFF | (state.pie ? 0x0400 : 0);	\
    3.20  				fault = true;										\
    3.21  				break;												\
    3.22  		}															\
    3.23 @@ -213,6 +215,52 @@
    3.24  	} while (0)
    3.25  /*}}}*/
    3.26  
    3.27 +bool access_check_dma(int reading)
    3.28 +{
    3.29 +	// Check memory access permissions
    3.30 +	bool access_ok;
    3.31 +	switch (checkMemoryAccess(state.dma_address, !reading)) {
    3.32 +		case MEM_PAGEFAULT:
    3.33 +			// Page fault
    3.34 +			state.genstat = 0xABFF
    3.35 +				| (reading ? 0x4000 : 0)
    3.36 +				| (state.pie ? 0x0400 : 0);
    3.37 +			access_ok = false;
    3.38 +			break;
    3.39 +
    3.40 +		case MEM_UIE:
    3.41 +			// User access to memory above 4MB
    3.42 +			// FIXME? Shouldn't be possible with DMA... assert this?
    3.43 +			state.genstat = 0xBAFF
    3.44 +				| (reading ? 0x4000 : 0)
    3.45 +				| (state.pie ? 0x0400 : 0);
    3.46 +			access_ok = false;
    3.47 +			break;
    3.48 +
    3.49 +		case MEM_KERNEL:
    3.50 +		case MEM_PAGE_NO_WE:
    3.51 +			// Kernel access or page not write enabled
    3.52 +			/* XXX: is this correct? */
    3.53 +			state.genstat = 0xBBFF
    3.54 +				| (reading ? 0x4000 : 0)
    3.55 +				| (state.pie ? 0x0400 : 0);
    3.56 +			access_ok = false;
    3.57 +			break;
    3.58 +
    3.59 +		case MEM_ALLOWED:
    3.60 +			access_ok = true;
    3.61 +			break;
    3.62 +	}
    3.63 +	if (!access_ok) {
    3.64 +		state.bsr0 = 0x3C00;
    3.65 +		state.bsr0 |= (state.dma_address >> 16);
    3.66 +		state.bsr1 = state.dma_address & 0xffff;
    3.67 +		if (state.ee) m68k_set_irq(7);
    3.68 +		printf("BUS ERROR FROM DMA: genstat=%04X, bsr0=%04X, bsr1=%04X\n", state.genstat, state.bsr0, state.bsr1);
    3.69 +	}
    3.70 +	return (access_ok);
    3.71 +}
    3.72 +
    3.73  // Logging macros
    3.74  #define LOG_NOT_HANDLED_R(bits)															\
    3.75  	if (!handled) printf("unhandled read%02d, addr=0x%08X\n", bits, address);
    3.76 @@ -275,9 +323,19 @@
    3.77  				state.idmarw = ((data & 0x4000) == 0x4000);
    3.78  				state.dmaen = ((data & 0x8000) == 0x8000);
    3.79  				// This handles the "dummy DMA transfer" mentioned in the docs
    3.80 -				// TODO: access check, peripheral access
    3.81 -				if (!state.idmarw)
    3.82 -					WR32(state.base_ram, mapAddr(address, true), state.base_ram_size - 1, 0xDEAD);
    3.83 +				// disabled because it causes the floppy test to fail
    3.84 +#if 0
    3.85 +				if (!state.idmarw){
    3.86 +					if (access_check_dma(true)){
    3.87 +						uint32_t newAddr = mapAddr(state.dma_address, true);
    3.88 +						// RAM access
    3.89 +						if (newAddr <= 0x1fffff)
    3.90 +							WR16(state.base_ram, newAddr, state.base_ram_size - 1, 0xFF);
    3.91 +						else if (address <= 0x3FFFFF)
    3.92 +							WR16(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1, 0xFF);
    3.93 +					}
    3.94 +				}
    3.95 +#endif
    3.96  				state.dma_count++;
    3.97  				handled = true;
    3.98  				break;
    3.99 @@ -316,7 +374,6 @@
   3.100  			case 0x0A0000:				// Miscellaneous Control Register
   3.101  				ENFORCE_SIZE_W(bits, address, 16, "MISCCON");
   3.102  				// TODO: handle the ctrl bits properly
   3.103 -				// TODO: &0x8000 --> dismiss 60hz intr
   3.104  				if (data & 0x8000){
   3.105  					state.timer_enabled = 1;
   3.106  				}else{
   3.107 @@ -353,16 +410,30 @@
   3.108  				handled = true;
   3.109  				break;
   3.110  			case 0x0E0000:				// Disk Control Register
   3.111 -				ENFORCE_SIZE_W(bits, address, 16, "DISKCON");
   3.112 -				// B7 = FDD controller reset
   3.113 -				if ((data & 0x80) == 0) wd2797_reset(&state.fdc_ctx);
   3.114 -				// B6 = drive 0 select -- TODO
   3.115 -				// B5 = motor enable -- TODO
   3.116 -				// B4 = HDD controller reset -- TODO
   3.117 -				// B3 = HDD0 select -- TODO
   3.118 -				// B2,1,0 = HDD0 head select
   3.119 -				handled = true;
   3.120 -				break;
   3.121 +				{
   3.122 +					bool fd_selected;
   3.123 +					bool hd_selected;
   3.124 +					ENFORCE_SIZE_W(bits, address, 16, "DISKCON");
   3.125 +					// B7 = FDD controller reset
   3.126 +					if ((data & 0x80) == 0) wd2797_reset(&state.fdc_ctx);
   3.127 +					// B6 = drive 0 select
   3.128 +					fd_selected = (data & 0x40) != 0;
   3.129 +					// B5 = motor enable -- TODO
   3.130 +					// B4 = HDD controller reset
   3.131 +					if ((data & 0x10) == 0) wd2010_reset(&state.hdc_ctx);
   3.132 +					// B3 = HDD0 select
   3.133 +					hd_selected = (data & 0x08) != 0;
   3.134 +					// B2,1,0 = HDD0 head select -- TODO?
   3.135 +					if (hd_selected && !state.hd_selected){
   3.136 +						state.fd_selected = false;
   3.137 +						state.hd_selected = true;
   3.138 +					}else if (fd_selected && !state.fd_selected){
   3.139 +						state.hd_selected = false;
   3.140 +						state.fd_selected = true;
   3.141 +					}
   3.142 +					handled = true;
   3.143 +					break;
   3.144 +				}
   3.145  			case 0x0F0000:				// Line Printer Data Register
   3.146  				break;
   3.147  		}
   3.148 @@ -388,14 +459,17 @@
   3.149  			case 0xE00000:				// HDC, FDC, MCR2 and RTC data bits
   3.150  			case 0xF00000:
   3.151  				switch (address & 0x070000) {
   3.152 -					case 0x000000:		// [ef][08]xxxx ==> WD1010 hard disc controller
   3.153 +					case 0x000000:		// [ef][08]xxxx ==> WD2010 hard disc controller
   3.154 +						wd2010_write_reg(&state.hdc_ctx, (address >> 1) & 7, data);
   3.155 +						handled = true;
   3.156  						break;
   3.157  					case 0x010000:		// [ef][19]xxxx ==> WD2797 floppy disc controller
   3.158 -						ENFORCE_SIZE_W(bits, address, 16, "FDC REGISTERS");
   3.159 +						/*ENFORCE_SIZE_W(bits, address, 16, "FDC REGISTERS");*/
   3.160  						wd2797_write_reg(&state.fdc_ctx, (address >> 1) & 3, data);
   3.161  						handled = true;
   3.162  						break;
   3.163  					case 0x020000:		// [ef][2a]xxxx ==> Miscellaneous Control Register 2
   3.164 +						/*TODO: implement P5.1 second hard drive select*/
   3.165  						break;
   3.166  					case 0x030000:		// [ef][3b]xxxx ==> Real Time Clock data bits
   3.167  						break;
   3.168 @@ -492,6 +566,7 @@
   3.169  			case 0x070000:				// Line Printer Status Register
   3.170  				data = 0x00120012;	// no parity error, no line printer error, no irqs from FDD or HDD
   3.171  				data |= wd2797_get_irq(&state.fdc_ctx) ? 0x00080008 : 0;
   3.172 +				data |= wd2010_get_irq(&state.hdc_ctx) ? 0x00040004 : 0;
   3.173  				return data;
   3.174  				break;
   3.175  			case 0x080000:				// Real Time Clock
   3.176 @@ -563,9 +638,11 @@
   3.177  			case 0xF00000:
   3.178  				switch (address & 0x070000) {
   3.179  					case 0x000000:		// [ef][08]xxxx ==> WD1010 hard disc controller
   3.180 +						return (wd2010_read_reg(&state.hdc_ctx, (address >> 1) & 7));
   3.181 +
   3.182  						break;
   3.183  					case 0x010000:		// [ef][19]xxxx ==> WD2797 floppy disc controller
   3.184 -						ENFORCE_SIZE_R(bits, address, 16, "FDC REGISTERS");
   3.185 +						/*ENFORCE_SIZE_R(bits, address, 16, "FDC REGISTERS");*/
   3.186  						return wd2797_read_reg(&state.fdc_ctx, (address >> 1) & 3);
   3.187  						break;
   3.188  					case 0x020000:		// [ef][2a]xxxx ==> Miscellaneous Control Register 2
   3.189 @@ -826,6 +903,7 @@
   3.190  	} else if (address <= 0x3FFFFF) {
   3.191  		// RAM access
   3.192  		uint32_t newAddr = mapAddr(address, true);
   3.193 +
   3.194  		if (newAddr <= 0x1fffff)
   3.195  			WR16(state.base_ram, newAddr, state.base_ram_size - 1, value);
   3.196  		else
     4.1 --- a/src/memory.h	Sat Nov 17 19:13:08 2012 +0000
     4.2 +++ b/src/memory.h	Sat Nov 17 19:18:29 2012 +0000
     4.3 @@ -70,4 +70,12 @@
     4.4   */
     4.5  uint32_t mapAddr(uint32_t addr, bool writing);
     4.6  
     4.7 +/**
     4.8 + * @brief   Check access flags for a DMA transfer and trigger an exception if
     4.9 + *          the access is not permitted
    4.10 + * @param   reading     true if reading from memory, false if writing
    4.11 + * @return  true if the access is permitted, false if not
    4.12 + */
    4.13 +bool access_check_dma(int reading);
    4.14 +
    4.15  #endif
     5.1 --- a/src/state.c	Sat Nov 17 19:13:08 2012 +0000
     5.2 +++ b/src/state.c	Sat Nov 17 19:18:29 2012 +0000
     5.3 @@ -3,6 +3,7 @@
     5.4  #include <malloc.h>
     5.5  #include <stdio.h>
     5.6  #include "wd279x.h"
     5.7 +#include "wd2010.h"
     5.8  #include "keyboard.h"
     5.9  #include "state.h"
    5.10  
    5.11 @@ -114,6 +115,7 @@
    5.12  
    5.13  	// Deinitialise the disc controller
    5.14  	wd2797_done(&state.fdc_ctx);
    5.15 +	wd2010_done(&state.hdc_ctx);
    5.16  }
    5.17  
    5.18  
     6.1 --- a/src/state.h	Sat Nov 17 19:13:08 2012 +0000
     6.2 +++ b/src/state.h	Sat Nov 17 19:18:29 2012 +0000
     6.3 @@ -5,6 +5,7 @@
     6.4  #include <stdint.h>
     6.5  #include <stdbool.h>
     6.6  #include "wd279x.h"
     6.7 +#include "wd2010.h"
     6.8  #include "keyboard.h"
     6.9  
    6.10  // Maximum size of the Boot PROMs. Must be a binary power of two.
    6.11 @@ -74,11 +75,19 @@
    6.12  	bool		dmaen;
    6.13  	bool		dmaenb;
    6.14  
    6.15 +	/// DMA device selection flags
    6.16 +	bool		fd_selected;
    6.17 +	bool       	hd_selected;
    6.18  	/// Floppy disc controller context
    6.19  	WD2797_CTX	fdc_ctx;
    6.20  	/// Current disc image file
    6.21  	FILE *fdc_disc;
    6.22  
    6.23 +	/// Hard disc controller context
    6.24 +	WD2010_CTX  hdc_ctx;
    6.25 +	FILE *hdc_disc0;
    6.26 +	FILE *hdc_disc1;
    6.27 +
    6.28  	/// Keyboard controller context
    6.29  	KEYBOARD_STATE	kbd;
    6.30  } S_state;
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/src/wd2010.c	Sat Nov 17 19:18:29 2012 +0000
     7.3 @@ -0,0 +1,430 @@
     7.4 +#include <stdint.h>
     7.5 +#include <stdbool.h>
     7.6 +#include <malloc.h>
     7.7 +#include "SDL.h"
     7.8 +#include "musashi/m68k.h"
     7.9 +#include "wd2010.h"
    7.10 +
    7.11 +#define WD2010_DEBUG
    7.12 +
    7.13 +#ifndef WD2010_DEBUG
    7.14 +#define NDEBUG
    7.15 +#endif
    7.16 +#include "utils.h"
    7.17 +
    7.18 +#define SEEK_DELAY 30
    7.19 +
    7.20 +#define CMD_ENABLE_RETRY 0x01
    7.21 +#define CMD_LONG_MODE 0x02
    7.22 +#define CMD_MULTI_SECTOR 0x04
    7.23 +#define CMD_INTRQ_WHEN_COMPLETE 0x08
    7.24 +
    7.25 +#define ER_BAD_BLOCK 0x80
    7.26 +#define ER_CRC 0x40
    7.27 +#define ER_ID_NOT_FOUND 0x10
    7.28 +#define ER_ABORTED_COMMAND 0x04
    7.29 +#define ER_NO_TK0 0x02
    7.30 +#define ER_NO_ADDRESS_MARK 0x01
    7.31 +
    7.32 +#define SR_BUSY 0x80
    7.33 +#define SR_READY 0x40
    7.34 +#define SR_WRITE_FAULT 0x20
    7.35 +#define SR_SEEK_COMPLETE 0x10
    7.36 +#define SR_DRQ 0x08
    7.37 +#define SR_CORRECTED 0x04
    7.38 +#define SR_COMMAND_IN_PROGRESS 0x02
    7.39 +#define SR_ERROR 0x01
    7.40 +
    7.41 +extern int cpu_log_enabled;
    7.42 +
    7.43 +/// WD2010 command constants
    7.44 +enum {
    7.45 +	CMD_MASK				= 0xF0,		///< Bit mask to detect command bits
    7.46 +	CMD_2010_EXT			= 0x00,		///< WD2010 extended commands (compute correction, set parameter)
    7.47 +	CMD_RESTORE				= 0x10,		///< Restore (recalibrate, seek to track 0)
    7.48 +	CMD_READ_SECTOR			= 0x20,		///< Read sector
    7.49 +	CMD_WRITE_SECTOR		= 0x30,		///< Write sector
    7.50 +	CMD_SCAN_ID				= 0x40,		///< Scan ID
    7.51 +	CMD_WRITE_FORMAT		= 0x50,		///< Write format
    7.52 +	CMD_SEEK				= 0x70,		///< Seek to given track
    7.53 +};
    7.54 +
    7.55 +int wd2010_init(WD2010_CTX *ctx, FILE *fp, int secsz, int spt, int heads)
    7.56 +{
    7.57 +	long filesize;
    7.58 +	wd2010_reset(ctx);
    7.59 +	// Start by finding out how big the image file is
    7.60 +	fseek(fp, 0, SEEK_END);
    7.61 +	filesize = ftell(fp);
    7.62 +	fseek(fp, 0, SEEK_SET);
    7.63 +
    7.64 +	// Now figure out how many tracks it contains
    7.65 +	int tracks = filesize / secsz / spt / heads;
    7.66 +	// Confirm...
    7.67 +	if (tracks < 1) {
    7.68 +		return WD2010_ERR_BAD_GEOM;
    7.69 +	}
    7.70 +
    7.71 +	// Allocate enough memory to store one disc track
    7.72 +	if (ctx->data) {
    7.73 +		free(ctx->data);
    7.74 +	}
    7.75 +	ctx->data = malloc(secsz * spt);
    7.76 +	if (!ctx->data)
    7.77 +		return WD2010_ERR_NO_MEMORY;
    7.78 +
    7.79 +	// Load the image and the geometry data
    7.80 +	ctx->disc_image = fp;
    7.81 +	ctx->geom_tracks = tracks;
    7.82 +	ctx->geom_secsz = secsz;
    7.83 +	ctx->geom_heads = heads;
    7.84 +	ctx->geom_spt = spt;
    7.85 +	return WD2010_ERR_OK;
    7.86 +
    7.87 +}
    7.88 +
    7.89 +void wd2010_reset(WD2010_CTX *ctx)
    7.90 +{
    7.91 +	// track, head and sector unknown
    7.92 +	ctx->track = ctx->head = ctx->sector = 0;
    7.93 +
    7.94 +	// no IRQ pending
    7.95 +	ctx->irq = false;
    7.96 +
    7.97 +	// no data available
    7.98 +	ctx->data_pos = ctx->data_len = 0;
    7.99 +
   7.100 +	// Status register clear, not busy
   7.101 +	ctx->status = 0;
   7.102 +
   7.103 +	ctx->sector_count = 0;
   7.104 +	ctx->sector_number = 0;
   7.105 +	ctx->cylinder_low_reg = 0;
   7.106 +	ctx->cylinder_high_reg = 0;
   7.107 +	ctx->sdh = 0;
   7.108 +}
   7.109 +
   7.110 +void wd2010_done(WD2010_CTX *ctx)
   7.111 +{
   7.112 +	// Reset the WD2010
   7.113 +	wd2010_reset(ctx);
   7.114 +
   7.115 +	// Free any allocated memory
   7.116 +	if (ctx->data) {
   7.117 +		free(ctx->data);
   7.118 +		ctx->data = NULL;
   7.119 +	}
   7.120 +}
   7.121 +
   7.122 +
   7.123 +bool wd2010_get_irq(WD2010_CTX *ctx)
   7.124 +{
   7.125 +	return ctx->irq;
   7.126 +}
   7.127 +
   7.128 +bool wd2010_get_drq(WD2010_CTX *ctx)
   7.129 +{
   7.130 +	return (ctx->drq && ctx->data_pos < ctx->data_len);
   7.131 +}
   7.132 +
   7.133 +void wd2010_dma_miss(WD2010_CTX *ctx)
   7.134 +{
   7.135 +	ctx->data_pos = ctx->data_len;
   7.136 +	ctx->write_pos = 0;
   7.137 +	ctx->status = SR_READY | SR_SEEK_COMPLETE;
   7.138 +	ctx->irq = true;
   7.139 +}
   7.140 +
   7.141 +uint8_t wd2010_read_data(WD2010_CTX *ctx)
   7.142 +{
   7.143 +	// If there's data in the buffer, return it. Otherwise return 0xFF.
   7.144 +	if (ctx->data_pos < ctx->data_len) {
   7.145 +		if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
   7.146 +			ctx->sector_count--;
   7.147 +			ctx->sector_number++;
   7.148 +		}
   7.149 +		// set IRQ if this is the last data byte
   7.150 +		if (ctx->data_pos == (ctx->data_len-1)) {
   7.151 +			ctx->status = SR_READY | SR_SEEK_COMPLETE;
   7.152 +			// Set IRQ
   7.153 +			ctx->irq = true;
   7.154 +			ctx->drq = false;
   7.155 +		}
   7.156 +		// return data byte and increment pointer
   7.157 +		return ctx->data[ctx->data_pos++];
   7.158 +	} else {
   7.159 +		// empty buffer (this shouldn't happen)
   7.160 +		LOG("WD2010: attempt to read from empty data buffer");
   7.161 +		return 0xff;
   7.162 +	}
   7.163 +}
   7.164 +
   7.165 +void wd2010_write_data(WD2010_CTX *ctx, uint8_t val)
   7.166 +{
   7.167 +	// If we're processing a write command, and there's space in the
   7.168 +	// buffer, allow the write.
   7.169 +	if (ctx->write_pos >= 0 && ctx->data_pos < ctx->data_len) {
   7.170 +		// store data byte and increment pointer
   7.171 +		if (ctx->multi_sector && ctx->data_pos & !(ctx->data_pos & ~(ctx->geom_secsz - 1))){
   7.172 +			ctx->sector_count--;
   7.173 +			ctx->sector_number++;
   7.174 +		}
   7.175 +		ctx->data[ctx->data_pos++] = val;
   7.176 +		// set IRQ and write data if this is the last data byte
   7.177 +		if (ctx->data_pos == ctx->data_len) {
   7.178 +			if (!ctx->formatting){
   7.179 +				fseek(ctx->disc_image, ctx->write_pos, SEEK_SET);
   7.180 +				fwrite(ctx->data, 1, ctx->data_len, ctx->disc_image);
   7.181 +				fflush(ctx->disc_image);
   7.182 +			}
   7.183 +			ctx->formatting = false;
   7.184 +			ctx->status = SR_READY | SR_SEEK_COMPLETE;
   7.185 +			// Set IRQ and reset write pointer
   7.186 +			ctx->irq = true;
   7.187 +			ctx->write_pos = -1;
   7.188 +			ctx->drq = false;
   7.189 +		}
   7.190 +	}else{
   7.191 +		LOG("WD2010: attempt to write to data buffer without a write command in progress");
   7.192 +	}
   7.193 +}
   7.194 +
   7.195 +uint32_t seek_complete(uint32_t interval, WD2010_CTX *ctx)
   7.196 +{
   7.197 +	/*m68k_end_timeslice();*/
   7.198 +	ctx->status = SR_READY | SR_SEEK_COMPLETE;
   7.199 +	ctx->irq = true;
   7.200 +	return (0);
   7.201 +}
   7.202 +
   7.203 +uint32_t transfer_seek_complete(uint32_t interval, WD2010_CTX *ctx)
   7.204 +{
   7.205 +	/*m68k_end_timeslice();*/
   7.206 +	ctx->drq = true;
   7.207 +	return (0);
   7.208 +}
   7.209 +
   7.210 +uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr)
   7.211 +{
   7.212 +	uint8_t temp = 0;
   7.213 +
   7.214 +	/*cpu_log_enabled = 1;*/
   7.215 +
   7.216 +	switch (addr & 0x07) {
   7.217 +		case WD2010_REG_ERROR:
   7.218 +			return ctx->error_reg;
   7.219 +		case WD2010_REG_SECTOR_COUNT:
   7.220 +			return ctx->sector_count;
   7.221 +		case WD2010_REG_SECTOR_NUMBER:
   7.222 +			return ctx->sector_number;
   7.223 +		case WD2010_REG_CYLINDER_HIGH:      // High byte of cylinder
   7.224 +			return ctx->cylinder_high_reg;
   7.225 +		case WD2010_REG_CYLINDER_LOW:       // Low byte of cylinder
   7.226 +			return ctx->cylinder_low_reg;
   7.227 +		case WD2010_REG_SDH:
   7.228 +			return ctx->sdh;
   7.229 +		case WD2010_REG_STATUS:             // Status register
   7.230 +			// Read from status register clears IRQ
   7.231 +			ctx->irq = false;
   7.232 +			// Get current status flags (set by last command)
   7.233 +			// DRQ bit
   7.234 +			if (ctx->cmd_has_drq) {
   7.235 +				temp = ctx->status & ~(SR_BUSY & SR_DRQ);
   7.236 +				temp |= (ctx->data_pos < ctx->data_len) ? SR_DRQ : 0;
   7.237 +				LOG("\tWDFDC rd sr, has drq, pos=%lu len=%lu, sr=0x%02X", ctx->data_pos, ctx->data_len, temp);
   7.238 +			} else {
   7.239 +				temp = ctx->status & ~0x80;
   7.240 +			}
   7.241 +			/*XXX: where should 0x02 (command in progress) be set? should it be set here instead of 0x80 (busy)?*/
   7.242 +			// HDC is busy if there is still data in the buffer
   7.243 +			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!
   7.244 +																	// TODO: also if seek delay / read delay hasn't passed (but that's for later)
   7.245 +			/*XXX: should anything else be set here?*/
   7.246 +			return temp;
   7.247 +		default:
   7.248 +			// shut up annoying compilers which don't recognise unreachable code when they see it
   7.249 +			// (here's looking at you, gcc!)
   7.250 +			return 0xff;
   7.251 +	}
   7.252 +}
   7.253 +
   7.254 +
   7.255 +void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val)
   7.256 +{
   7.257 +	uint8_t cmd = val & CMD_MASK;
   7.258 +	size_t lba;
   7.259 +	int new_track;
   7.260 +	int sector_count;
   7.261 +
   7.262 +	m68k_end_timeslice();
   7.263 +
   7.264 +	/*cpu_log_enabled = 1;*/
   7.265 +
   7.266 +	switch (addr & 0x07) {
   7.267 +		case WD2010_REG_WRITE_PRECOMP_CYLINDER:
   7.268 +			break;
   7.269 +		case WD2010_REG_SECTOR_COUNT:
   7.270 +			ctx->sector_count = val;
   7.271 +			break;
   7.272 +		case WD2010_REG_SECTOR_NUMBER:
   7.273 +			ctx->sector_number = val;
   7.274 +			break;
   7.275 +		case WD2010_REG_CYLINDER_HIGH:		// High byte of cylinder
   7.276 +			ctx->cylinder_high_reg = val;
   7.277 +			break;
   7.278 +		case WD2010_REG_CYLINDER_LOW:		// Low byte of cylinder
   7.279 +			ctx->cylinder_low_reg = val;
   7.280 +			break;
   7.281 +		case WD2010_REG_SDH:
   7.282 +			/*XXX: remove this once the DMA page fault test passes (unless this is actually the correct behavior here)*/
   7.283 +			ctx->data_pos = ctx->data_len = 0;
   7.284 +			ctx->sdh = val;
   7.285 +			break;
   7.286 +		case WD2010_REG_COMMAND:	// Command register
   7.287 +			// write to command register clears interrupt request
   7.288 +			ctx->irq = false;
   7.289 +			ctx->error_reg = 0;
   7.290 +
   7.291 +			/*cpu_log_enabled = 1;*/
   7.292 +			switch (cmd) {
   7.293 +				case CMD_RESTORE:
   7.294 +					// Restore. Set track to 0 and throw an IRQ.
   7.295 +					ctx->track = 0;
   7.296 +					SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
   7.297 +					break;
   7.298 +				case CMD_SCAN_ID:
   7.299 +					ctx->cylinder_high_reg = (ctx->track >> 8) & 0xff;
   7.300 +					ctx->cylinder_low_reg = ctx->track & 0xff;
   7.301 +					ctx->sector_number = ctx->sector;
   7.302 +					ctx->sdh = (ctx->sdh & ~7) | (ctx->head & 7);
   7.303 +				case CMD_WRITE_FORMAT:
   7.304 +				case CMD_SEEK:
   7.305 +				case CMD_READ_SECTOR:
   7.306 +				case CMD_WRITE_SECTOR:
   7.307 +					// Seek. Seek to the track specced in the cylinder
   7.308 +					// registers.
   7.309 +					new_track = (ctx->cylinder_high_reg << 8) | ctx->cylinder_low_reg;
   7.310 +					if (new_track < ctx->geom_tracks) {
   7.311 +						ctx->track = new_track;
   7.312 +					} else {
   7.313 +						// Seek error. :(
   7.314 +						ctx->status = SR_ERROR;
   7.315 +						ctx->error_reg = ER_ID_NOT_FOUND;
   7.316 +						ctx->irq = true;
   7.317 +						break;
   7.318 +					}
   7.319 +					ctx->head = ctx->sdh & 0x07;
   7.320 +					ctx->sector = ctx->sector_number;
   7.321 +
   7.322 +					ctx->formatting = cmd == CMD_WRITE_FORMAT;
   7.323 +					switch (cmd){
   7.324 +						case CMD_SEEK:
   7.325 +							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)seek_complete, ctx);
   7.326 +							break;
   7.327 +						case CMD_READ_SECTOR:
   7.328 +							/*XXX: does a separate function to set the head have to be added?*/
   7.329 +							LOG("WD2010: READ SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   7.330 +
   7.331 +							// Read Sector
   7.332 +
   7.333 +							// Check to see if the cyl, hd and sec are valid
   7.334 +							if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt - 1)) {
   7.335 +								LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   7.336 +										ctx->track, ctx->head, ctx->sector,
   7.337 +										ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt-1);
   7.338 +								// CHS parameters exceed limits
   7.339 +								ctx->status = SR_ERROR;
   7.340 +								ctx->error_reg = ER_ID_NOT_FOUND;
   7.341 +								// Set IRQ
   7.342 +								ctx->irq = true;
   7.343 +								break;
   7.344 +							}
   7.345 +
   7.346 +							// reset data pointers
   7.347 +							ctx->data_pos = ctx->data_len = 0;
   7.348 +
   7.349 +							if (val & CMD_MULTI_SECTOR){
   7.350 +								ctx->multi_sector = 1;
   7.351 +								sector_count = ctx->sector_count;
   7.352 +							}else{
   7.353 +								ctx->multi_sector = 0;
   7.354 +								sector_count = 1;
   7.355 +							}
   7.356 +							for (int i=0; i<sector_count; i++) {
   7.357 +								// Calculate the LBA address of the required sector
   7.358 +								// LBA = (C * nHeads * nSectors) + (H * nSectors) + S - 1
   7.359 +								lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector) + i);
   7.360 +								// convert LBA to byte address
   7.361 +								lba *= ctx->geom_secsz;
   7.362 +								LOG("\tREAD lba = %lu", lba);
   7.363 +
   7.364 +								// Read the sector from the file
   7.365 +								fseek(ctx->disc_image, lba, SEEK_SET);
   7.366 +								// TODO: check fread return value! if < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   7.367 +								ctx->data_len += fread(&ctx->data[ctx->data_len], 1, ctx->geom_secsz, ctx->disc_image);
   7.368 +								LOG("\tREAD len=%lu, pos=%lu, ssz=%d", ctx->data_len, ctx->data_pos, ctx->geom_secsz);
   7.369 +							}
   7.370 +
   7.371 +							ctx->status = 0;
   7.372 +							ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
   7.373 +							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
   7.374 +
   7.375 +							break;
   7.376 +						case CMD_WRITE_FORMAT:
   7.377 +							ctx->sector = 0;
   7.378 +						case CMD_WRITE_SECTOR:
   7.379 +							LOG("WD2010: WRITE SECTOR cmd=%02X chs=%d:%d:%d", cmd, ctx->track, ctx->head, ctx->sector);
   7.380 +							// Read Sector
   7.381 +
   7.382 +							// Check to see if the cyl, hd and sec are valid
   7.383 +							if ((ctx->track > (ctx->geom_tracks-1)) || (ctx->head > (ctx->geom_heads-1)) || (ctx->sector > ctx->geom_spt-1)) {
   7.384 +								LOG("*** WD2010 ALERT: CHS parameter limit exceeded! CHS=%d:%d:%d, maxCHS=%d:%d:%d",
   7.385 +										ctx->track, ctx->head, ctx->sector,
   7.386 +										ctx->geom_tracks-1, ctx->geom_heads-1, ctx->geom_spt);
   7.387 +								// CHS parameters exceed limits
   7.388 +								ctx->status = SR_ERROR;
   7.389 +								ctx->error_reg = ER_ID_NOT_FOUND;
   7.390 +								// Set IRQ
   7.391 +								ctx->irq = true;
   7.392 +								break;
   7.393 +							}
   7.394 +
   7.395 +							// reset data pointers
   7.396 +							ctx->data_pos = ctx->data_len = 0;
   7.397 +
   7.398 +							if (val & CMD_MULTI_SECTOR){
   7.399 +								ctx->multi_sector = 1;
   7.400 +								sector_count = ctx->sector_count;
   7.401 +							}else{
   7.402 +								ctx->multi_sector = 0;
   7.403 +								sector_count = 1;
   7.404 +							}
   7.405 +							ctx->data_len = ctx->geom_secsz * sector_count;
   7.406 +							lba = (((ctx->track * ctx->geom_heads * ctx->geom_spt) + (ctx->head * ctx->geom_spt) + ctx->sector));
   7.407 +							// convert LBA to byte address
   7.408 +							ctx->write_pos = lba * ctx->geom_secsz;
   7.409 +							LOG("\tWRITE lba = %lu", lba);
   7.410 +
   7.411 +							ctx->status = 0;
   7.412 +							ctx->status |= (ctx->data_pos < ctx->data_len) ? SR_DRQ | SR_COMMAND_IN_PROGRESS | SR_BUSY : 0x00;
   7.413 +							SDL_AddTimer(SEEK_DELAY, (SDL_NewTimerCallback)transfer_seek_complete, ctx);
   7.414 +
   7.415 +							break;
   7.416 +						default:
   7.417 +							LOG("WD2010: invalid seeking command %x (this shouldn't happen!)\n", cmd);
   7.418 +							break;
   7.419 +					}
   7.420 +					break;
   7.421 +				case CMD_2010_EXT: /* not implemented */
   7.422 +				default:
   7.423 +					LOG("WD2010: unknown command %x\n", cmd);
   7.424 +					ctx->status = SR_ERROR;
   7.425 +					ctx->error_reg = ER_ABORTED_COMMAND;
   7.426 +					ctx->irq = true;
   7.427 +					break;
   7.428 +			}
   7.429 +			break;
   7.430 +
   7.431 +	}
   7.432 +}
   7.433 +
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/src/wd2010.h	Sat Nov 17 19:18:29 2012 +0000
     8.3 @@ -0,0 +1,127 @@
     8.4 +#ifndef _WD2010_H
     8.5 +#define _WD2010_H
     8.6 +
     8.7 +#include <stdbool.h>
     8.8 +#include <stddef.h>
     8.9 +#include <stdint.h>
    8.10 +#include <stdio.h>
    8.11 +
    8.12 +/// WD2010 registers
    8.13 +typedef enum {
    8.14 +	WD2010_REG_ERROR                   = 1, ///< Error register
    8.15 +	WD2010_REG_WRITE_PRECOMP_CYLINDER  = 1, ///< Write precompensation cylinder
    8.16 +                                             ///< register
    8.17 +	WD2010_REG_SECTOR_COUNT            = 2, ///< Sector count register
    8.18 +	WD2010_REG_SECTOR_NUMBER           = 3, ///< Sector number register
    8.19 +	WD2010_REG_CYLINDER_LOW            = 4, ///< Low byte of cylinder
    8.20 +	WD2010_REG_CYLINDER_HIGH           = 5, ///< High byte of cylinder
    8.21 +	WD2010_REG_SDH                     = 6, ///< Sector size, drive, and head
    8.22 +	WD2010_REG_STATUS                   = 7, ///< Status register
    8.23 +	WD2010_REG_COMMAND                  = 7, ///< Command register
    8.24 +} WD2010_REG;
    8.25 +
    8.26 +/// WD2010 emulator error codes
    8.27 +typedef enum {
    8.28 +	WD2010_ERR_OK			= 0,		///< Operation succeeded
    8.29 +	WD2010_ERR_BAD_GEOM		= -1,		///< Bad geometry, or image file too small
    8.30 +	WD2010_ERR_NO_MEMORY	= -2		///< Out of memory
    8.31 +} WD2010_ERR;
    8.32 +
    8.33 +typedef struct {
    8.34 +	// Current track, head and sector
    8.35 +	int						track, head, sector;
    8.36 +	// Geometry of current disc
    8.37 +	int						geom_secsz, geom_spt, geom_heads, geom_tracks;
    8.38 +	// IRQ status
    8.39 +	bool					irq;
    8.40 +	// Status of last command
    8.41 +	uint8_t					status;
    8.42 +	// Error resgister
    8.43 +	uint8_t					error_reg;
    8.44 +	// Cylinder number registers
    8.45 +	uint8_t					cylinder_high_reg, cylinder_low_reg;
    8.46 +	// SDH register (sets sector size, drive number, and head number)
    8.47 +	uint8_t					sdh;
    8.48 +	// Sector number and count registers
    8.49 +	int						sector_number, sector_count;
    8.50 +	// Last command has the multiple sector flag set?
    8.51 +	bool					multi_sector;
    8.52 +	// Last command uses DRQ bit?
    8.53 +	bool					cmd_has_drq;
    8.54 +	// Current write is a format?
    8.55 +	bool					formatting;
    8.56 +	// Data buffer, current DRQ pointer and length
    8.57 +	uint8_t					*data;
    8.58 +	size_t					data_pos, data_len;
    8.59 +	// Current disc image file
    8.60 +	FILE					*disc_image;
    8.61 +	// LBA at which to start writing
    8.62 +	int						write_pos;
    8.63 +	// Flag to allow delaying DRQ
    8.64 +	bool					drq;
    8.65 +} WD2010_CTX;
    8.66 +
    8.67 +/**
    8.68 + * @brief	Initialise a WD2010 context.
    8.69 + * @param	ctx		WD2010 context.
    8.70 + *
    8.71 + * This must be run once when the context is created.
    8.72 + */
    8.73 +int wd2010_init(WD2010_CTX *ctx, FILE *fp, int secsz, int spt, int heads);
    8.74 +
    8.75 +/**
    8.76 + * @brief	Reset a WD2010 context.
    8.77 + * @param	ctx		WD2010 context.
    8.78 + *
    8.79 + * This should be run if the WD2010 needs to be reset (MR/ line toggled).
    8.80 + */
    8.81 +void wd2010_reset(WD2010_CTX *ctx);
    8.82 +
    8.83 +/**
    8.84 + * Deinitialise a WD2010 context.
    8.85 + * @param	ctx		WD2010 context.
    8.86 + */
    8.87 +void wd2010_done(WD2010_CTX *ctx);
    8.88 +
    8.89 +/**
    8.90 + * @brief	Read IRQ Rising Edge status.
    8.91 + * @param	ctx		WD2010 context.
    8.92 + */
    8.93 +bool wd2010_get_irq(WD2010_CTX *ctx);
    8.94 +
    8.95 +/**
    8.96 + * @brief	Read DRQ status.
    8.97 + * @param	ctx		WD2010 context.
    8.98 + */
    8.99 +bool wd2010_get_drq(WD2010_CTX *ctx);
   8.100 +
   8.101 +/**
   8.102 + * @brief	Read WD2010 register.
   8.103 + * @param	ctx		WD2010 context
   8.104 + * @param	addr	Register address (0, 1, 2, 3, 4, 5, 6, 7, or 8)
   8.105 + */
   8.106 +uint8_t wd2010_read_reg(WD2010_CTX *ctx, uint8_t addr);
   8.107 +
   8.108 +/**
   8.109 + * @brief	Write WD2010 register
   8.110 + * @param	ctx		WD2010 context
   8.111 + * @param	addr	Register address (0, 1, 2, 3, 4, 5, 6, 7, or 8)
   8.112 + * @param	val		Value to write
   8.113 + */
   8.114 +void wd2010_write_reg(WD2010_CTX *ctx, uint8_t addr, uint8_t val);
   8.115 +
   8.116 +/**
   8.117 + * @brief   Read a data byte from the data buffer
   8.118 + * @param	ctx		WD2010 context
   8.119 + */
   8.120 +uint8_t wd2010_read_data(WD2010_CTX *ctx);
   8.121 +
   8.122 +/**
   8.123 + * @brief   Write a value to the data buffer
   8.124 + * @param	ctx		WD2010 context
   8.125 + * @param   val     Value to write
   8.126 + */
   8.127 +void wd2010_write_data(WD2010_CTX *ctx, uint8_t val);
   8.128 +
   8.129 +void wd2010_dma_miss(WD2010_CTX *ctx);
   8.130 +#endif