src/main.c

Tue, 15 Nov 2011 10:12:37 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Tue, 15 Nov 2011 10:12:37 +0000
changeset 109
2f8afb9e5baa
parent 107
566cfc70ef33
child 111
4c85846b09cd
permissions
-rw-r--r--

[musashi] Fix handling of bus errors

Patch-Author: Andrew Warkentin <andreww591!gmail>
Patch-MessageID: <4EC200CE.2020304@gmail.com>

I have fixed the first page fault test failure in FreeBee (the page fault test now hangs rather than errors out, because it is trying to read from the hard drive to test DMA page faults).

There were actually two bugs (the first bug was masking the second one).

First, the ancient version of Musashi that you used is unable to properly resume from bus errors that happen in the middle of certain instructions (some instructions are fetched in stages, with the PC being advanced to each part of the instruction, so basically what happens is the CPU core attempts to read the memory location referenced by the first operand, the bus error occurs, causing the PC to jump to the exception vector, but the faulting instruction is still in the middle of being fetched, so the PC is then advanced past the beginning of the exception handler). I fixed this by delaying the jump to the bus error vector until after the faulting instruction finishes.

The second bug is simpler - you had the UDS and LDS bits in BSR0 inverted (they are supposed to be active low).

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <stdint.h>
     4 #include <stdbool.h>
     5 #include <malloc.h>
     6 #include <string.h>
     8 #include "SDL.h"
    10 #include "musashi/m68k.h"
    11 #include "version.h"
    12 #include "state.h"
    13 #include "memory.h"
    15 void FAIL(char *err)
    16 {
    17 	state_done();
    18 	fprintf(stderr, "ERROR: %s\nExiting...\n", err);
    19 	exit(EXIT_FAILURE);
    20 }
    22 /**
    23  * @brief Set the pixel at (x, y) to the given value
    24  * @note The surface must be locked before calling this!
    25  * @param	surface		SDL surface upon which to draw
    26  * @param	x			X co-ordinate
    27  * @param	y			Y co-ordinate
    28  * @param	pixel		Pixel value (from SDL_MapRGB)
    29  */
    30 void putpixel(SDL_Surface *surface, int x, int y, Uint32 pixel)
    31 {
    32 	int bpp = surface->format->BytesPerPixel;
    33 	/* Here p is the address to the pixel we want to set */
    34 	Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;
    36 	switch (bpp) {
    37 		case 1:
    38 			*p = pixel;
    39 			break;
    41 		case 2:
    42 			*(Uint16 *)p = pixel;
    43 			break;
    45 		case 3:
    46 			if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
    47 				p[0] = (pixel >> 16) & 0xff;
    48 				p[1] = (pixel >> 8) & 0xff;
    49 				p[2] = pixel & 0xff;
    50 			}
    51 			else {
    52 				p[0] = pixel & 0xff;
    53 				p[1] = (pixel >> 8) & 0xff;
    54 				p[2] = (pixel >> 16) & 0xff;
    55 			}
    56 			break;
    58 		case 4:
    59 			*(Uint32 *)p = pixel;
    60 			break;
    62 		default:
    63 			break;           /* shouldn't happen, but avoids warnings */
    64 	} // switch
    65 }
    68 /**
    69  * @brief	Refresh the screen.
    70  * @param	surface		SDL surface upon which to draw.
    71  */
    72 void refreshScreen(SDL_Surface *s)
    73 {
    74 	// Lock the screen surface (if necessary)
    75 	if (SDL_MUSTLOCK(s)) {
    76 		if (SDL_LockSurface(s) < 0) {
    77 			fprintf(stderr, "ERROR: Unable to lock screen!\n");
    78 			exit(EXIT_FAILURE);
    79 		}
    80 	}
    82 	// Map the foreground and background colours
    83 	Uint32 fg = SDL_MapRGB(s->format, 0x00, 0xFF, 0x00);	// green foreground
    84 //	Uint32 fg = SDL_MapRGB(s->format, 0xFF, 0xC1, 0x06);	// amber foreground
    85 //	Uint32 fg = SDL_MapRGB(s->format, 0xFF, 0xFF, 0xFF);	// white foreground
    86 	Uint32 bg = SDL_MapRGB(s->format, 0x00, 0x00, 0x00);	// black background
    88 	// Refresh the 3B1 screen area first. TODO: only do this if VRAM has actually changed!
    89 	uint32_t vram_address = 0;
    90 	for (int y=0; y<348; y++) {
    91 		for (int x=0; x<720; x+=16) {	// 720 pixels, monochrome, packed into 16bit words
    92 			// Get the pixel
    93 			uint16_t val = RD16(state.vram, vram_address, sizeof(state.vram)-1);
    94 			vram_address += 2;
    95 			// Now copy it to the video buffer
    96 			for (int px=0; px<16; px++) {
    97 				if (val & 1)
    98 					putpixel(s, x+px, y, fg);
    99 				else
   100 					putpixel(s, x+px, y, bg);
   101 				val >>= 1;
   102 			}
   103 		}
   104 	}
   106 	// TODO: blit LEDs and status info
   108 	// Unlock the screen surface
   109 	if (SDL_MUSTLOCK(s)) {
   110 		SDL_UnlockSurface(s);
   111 	}
   113 	// Trigger a refresh -- TODO: partial refresh depending on whether we
   114 	// refreshed the screen area, status area, both, or none. Use SDL_UpdateRect() for this.
   115 	SDL_Flip(s);
   116 }
   118 /**
   119  * @brief	Handle events posted by SDL.
   120  */
   121 bool HandleSDLEvents(SDL_Surface *screen)
   122 {
   123 	SDL_Event event;
   124 	while (SDL_PollEvent(&event))
   125 	{
   126 		if ((event.type == SDL_KEYDOWN) || (event.type == SDL_KEYUP)) {
   127 			keyboard_event(&state.kbd, &event);
   128 		}
   130 		switch (event.type) {
   131 			case SDL_QUIT:
   132 				// Quit button tagged. Exit.
   133 				return true;
   134 			case SDL_KEYDOWN:
   135 				switch (event.key.keysym.sym) {
   136 					case SDLK_F11:
   137 						if (state.fdc_disc) {
   138 							wd2797_unload(&state.fdc_ctx);
   139 							fclose(state.fdc_disc);
   140 							state.fdc_disc = NULL;
   141 							fprintf(stderr, "Disc image unloaded.\n");
   142 						} else {
   143 							state.fdc_disc = fopen("discim", "rb");
   144 							if (!state.fdc_disc) {
   145 								fprintf(stderr, "ERROR loading disc image 'discim'.\n");
   146 								state.fdc_disc = NULL;
   147 							} else {
   148 								wd2797_load(&state.fdc_ctx, state.fdc_disc, 512, 10, 2);
   149 								fprintf(stderr, "Disc image loaded.\n");
   150 							}
   151 						}
   152 						break;
   153 					case SDLK_F12:
   154 						if (event.key.keysym.mod & (KMOD_LALT | KMOD_RALT))
   155 							// ALT-F12 pressed; exit emulator
   156 							return true;
   157 						break;
   158 					default:
   159 						break;
   160 				}
   161 				break;
   162 			default:
   163 				break;
   164 		}
   165 	}
   167 	return false;
   168 }
   171 /****************************
   172  * blessed be thy main()...
   173  ****************************/
   175 int main(void)
   176 {
   177 	// copyright banner
   178 	printf("FreeBee: A Quick-and-Dirty AT&T 3B1 Emulator. Version %s, %s mode.\n", VER_FULLSTR, VER_BUILD_TYPE);
   179 	printf("Copyright (C) 2010 P. A. Pemberton. All rights reserved.\nLicensed under the Apache License Version 2.0.\n");
   180 	printf("Musashi M680x0 emulator engine developed by Karl Stenerud <kstenerud@gmail.com>\n");
   181 	printf("Built %s by %s@%s.\n", VER_COMPILE_DATETIME, VER_COMPILE_BY, VER_COMPILE_HOST);
   182 	printf("Compiler: %s\n", VER_COMPILER);
   183 	printf("CFLAGS: %s\n", VER_CFLAGS);
   184 	printf("\n");
   186 	// set up system state
   187 	// 512K of RAM
   188 	int i;
   189 	if ((i = state_init(512*1024, 512*1024)) != STATE_E_OK) {
   190 		fprintf(stderr, "ERROR: Emulator initialisation failed. Error code %d.\n", i);
   191 		return i;
   192 	}
   194 	// set up musashi and reset the CPU
   195 	m68k_set_cpu_type(M68K_CPU_TYPE_68010);
   196 	m68k_pulse_reset();
   198 	// Set up SDL
   199 	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) == -1) {
   200 		printf("Could not initialise SDL: %s.\n", SDL_GetError());
   201 		exit(EXIT_FAILURE);
   202 	}
   204 	// Make sure SDL cleans up after itself
   205 	atexit(SDL_Quit);
   207 	// Set up the video display
   208 	SDL_Surface *screen = NULL;
   209 	if ((screen = SDL_SetVideoMode(720, 384, 8, SDL_SWSURFACE | SDL_ANYFORMAT)) == NULL) {
   210 		printf("Could not find a suitable video mode: %s.\n", SDL_GetError());
   211 		exit(EXIT_FAILURE);
   212 	}
   213 	printf("Set %dx%d at %d bits-per-pixel mode\n\n", screen->w, screen->h, screen->format->BitsPerPixel);
   214 	SDL_WM_SetCaption("FreeBee 3B1 emulator", "FreeBee");
   216 	// Load a disc image
   217 	state.fdc_disc = fopen("discim", "rb");
   218 	if (!state.fdc_disc) {
   219 		fprintf(stderr, "ERROR loading disc image 'discim'.\n");
   220 		return -4;
   221 	}
   222 	wd2797_load(&state.fdc_ctx, state.fdc_disc, 512, 10, 2);
   224 	/***
   225 	 * The 3B1 CPU runs at 10MHz, with DMA running at 1MHz and video refreshing at
   226 	 * around 60Hz (???), with a 60Hz periodic interrupt.
   227 	 */
   228 	const uint32_t SYSTEM_CLOCK = 10e6; // Hz
   229 	const uint32_t TIMESLOT_FREQUENCY = 1000;//240;	// Hz
   230 	const uint32_t MILLISECS_PER_TIMESLOT = 1e3 / TIMESLOT_FREQUENCY;
   231 	const uint32_t CLOCKS_PER_60HZ = (SYSTEM_CLOCK / 60);
   232 	uint32_t next_timeslot = SDL_GetTicks() + MILLISECS_PER_TIMESLOT;
   233 	uint32_t clock_cycles = 0, tmp;
   234 	bool exitEmu = false;
   235 	bool lastirq_fdc = false;
   236 	for (;;) {
   237 		// Run the CPU for however many cycles we need to. CPU core clock is
   238 		// 10MHz, and we're running at 240Hz/timeslot. Thus: 10e6/240 or
   239 		// 41667 cycles per timeslot.
   240 		tmp = m68k_execute(SYSTEM_CLOCK/TIMESLOT_FREQUENCY);
   241 		clock_cycles += tmp;
   243 		// Run the DMA engine
   244 		if (state.dmaen) {
   245 			// DMA ready to go -- so do it.
   246 			size_t num = 0;
   247 			while (state.dma_count < 0x4000) {
   248 				uint16_t d = 0;
   250 				// num tells us how many words we've copied. If this is greater than the per-timeslot DMA maximum, bail out!
   251 				if (num > (1e6/TIMESLOT_FREQUENCY)) break;
   253 				// Evidently we have more words to copy. Copy them.
   254 				if (!wd2797_get_drq(&state.fdc_ctx)) {
   255 					// Bail out, no data available. Try again later.
   256 					// TODO: handle HDD controller too
   257 					break;
   258 				}
   260 				// Check memory access permissions
   261 				// TODO: enforce these!!!! use ACCESS_CHECK_* for guidance.
   262 				bool access_ok;
   263 				switch (checkMemoryAccess(state.dma_address, !state.dma_reading)) {
   264 					case MEM_PAGEFAULT:
   265 						// Page fault
   266 						state.genstat = 0x8BFF
   267 							| (state.dma_reading ? 0x4000 : 0)
   268 							| (state.pie ? 0x0400 : 0);
   269 						access_ok = false;
   270 						break;
   272 					case MEM_UIE:
   273 						// User access to memory above 4MB
   274 						// FIXME? Shouldn't be possible with DMA... assert this?
   275 						state.genstat = 0x9AFF
   276 							| (state.dma_reading ? 0x4000 : 0)
   277 							| (state.pie ? 0x0400 : 0);
   278 						access_ok = false;
   279 						break;
   281 					case MEM_KERNEL:
   282 					case MEM_PAGE_NO_WE:
   283 						// Kernel access or page not write enabled
   284 						access_ok = false;
   285 						break;
   287 					case MEM_ALLOWED:
   288 						access_ok = true;
   289 						break;
   290 				}
   291 				if (!access_ok) {
   292 					state.bsr0 = 0x3C00;
   293 					state.bsr0 |= (state.dma_address >> 16);
   294 					state.bsr1 = state.dma_address & 0xffff;
   295 					if (state.ee) m68k_pulse_bus_error();
   296 					printf("BUS ERROR FROM DMA: genstat=%04X, bsr0=%04X, bsr1=%04X\n", state.genstat, state.bsr0, state.bsr1);
   298 					// TODO: FIXME: if we get a pagefault, it NEEDS to be tagged as 'peripheral sourced'... this is a HACK!
   299 					printf("REALLY BIG FSCKING HUGE ERROR: DMA Memory Access caused a FAULT!\n");
   300 					exit(-1);
   301 				}
   303 				// Map logical address to a physical RAM address
   304 				uint32_t newAddr = mapAddr(state.dma_address, !state.dma_reading);
   306 				if (!state.dma_reading) {
   307 					// Data available. Get it from the FDC. TODO: handle HDD too
   308 					d = wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA);
   309 					d <<= 8;
   310 					d += wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA);
   312 					if (newAddr <= 0x1FFFFF) {
   313 						WR16(state.base_ram, newAddr, state.base_ram_size - 1, d);
   314 					} else if (newAddr >= 0x200000) {
   315 						WR16(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1, d);
   316 					}
   317 					m68k_write_memory_16(state.dma_address, d);
   318 				} else {
   319 					// Data write to FDC. TODO: handle HDD too.
   321 					// Get the data from RAM
   322 					if (newAddr <= 0x1fffff) {
   323 						d = RD16(state.base_ram, newAddr, state.base_ram_size - 1);
   324 					} else {
   325 						if (newAddr <= (state.exp_ram_size + 0x200000 - 1))
   326 							d = RD16(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1);
   327 						else
   328 							d = 0xffff;
   329 					}
   331 					// Send the data to the FDD
   332 					wd2797_write_reg(&state.fdc_ctx, WD2797_REG_DATA, (d >> 8));
   333 					wd2797_write_reg(&state.fdc_ctx, WD2797_REG_DATA, (d & 0xff));
   334 				}
   336 				// Increment DMA address
   337 				state.dma_address+=2;
   338 				// Increment number of words transferred
   339 				num++; state.dma_count++;
   340 			}
   342 			// Turn off DMA engine if we finished this cycle
   343 			if (state.dma_count >= 0x4000) {
   344 				// FIXME? apparently this isn't required... or is it?
   345 //				state.dma_count = 0;
   346 				state.dmaen = false;
   347 			}
   348 		}
   350 		// Any interrupts? --> TODO: masking
   351 /*		if (!lastirq_fdc) {
   352 			if (wd2797_get_irq(&state.fdc_ctx)) {
   353 				lastirq_fdc = true;
   354 				m68k_set_irq(2);
   355 			}
   356 */
   357 		if (keyboard_get_irq(&state.kbd)) {
   358 			m68k_set_irq(3);
   359 		} else {
   360 			lastirq_fdc = wd2797_get_irq(&state.fdc_ctx);
   361 //			if (!state.timer_asserted){
   362 //				m68k_set_irq(0);
   363 //			}
   364 		}
   366 		// Is it time to run the 60Hz periodic interrupt yet?
   367 		if (clock_cycles > CLOCKS_PER_60HZ) {
   368 			// Refresh the screen
   369 			refreshScreen(screen);
   370 			if (state.timer_enabled){
   371 				m68k_set_irq(6);
   372 				state.timer_asserted = true;
   373 			}
   374 			// scan the keyboard
   375 			keyboard_scan(&state.kbd);
   376 			// decrement clock cycle counter, we've handled the intr.
   377 			clock_cycles -= CLOCKS_PER_60HZ;
   378 		}
   380 		// handle SDL events -- returns true if we need to exit
   381 		if (HandleSDLEvents(screen))
   382 			exitEmu = true;
   384 		// make sure frame rate is equal to real time
   385 		uint32_t now = SDL_GetTicks();
   386 		if (now < next_timeslot) {
   387 			// timeslot finished early -- eat up some time
   388 			SDL_Delay(next_timeslot - now);
   389 		} else {
   390 			// timeslot finished late -- skip ahead to gain time
   391 			// TODO: if this happens a lot, we should let the user know
   392 			// that their PC might not be fast enough...
   393 			next_timeslot = now;
   394 		}
   395 		// advance to the next timeslot
   396 		next_timeslot += MILLISECS_PER_TIMESLOT;
   398 		// if we've been asked to exit the emulator, then do so.
   399 		if (exitEmu) break;
   400 	}
   402 	// Release the disc image
   403 	wd2797_unload(&state.fdc_ctx);
   404 	fclose(state.fdc_disc);
   406 	return 0;
   407 }