src/main.c

Fri, 04 Mar 2011 02:12:25 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Fri, 04 Mar 2011 02:12:25 +0000
changeset 107
566cfc70ef33
parent 97
240e195e4bed
child 111
4c85846b09cd
permissions
-rw-r--r--

only strobe BUSERR if dma access caused a pagefault, and don't send IRQ0s (musashi auto-clears IRQs)!

     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 }