src/main.c

Fri, 18 Jan 2013 17:20:07 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Fri, 18 Jan 2013 17:20:07 +0000
changeset 130
1fc7d607dbb4
parent 127
4c03f6433d0d
child 128
3246b74d96bc
child 134
b826697f411a
permissions
-rw-r--r--

[wd2010] disable DMA pagefault workaround (no longer required)

     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 static int load_fd()
    23 {
    25 	int writeable = 1;
    26 	state.fdc_disc = fopen("discim", "r+b");
    27 	if (!state.fdc_disc){
    28 		writeable = 0;
    29 		state.fdc_disc = fopen("discim", "rb");
    30 	}
    31 	if (!state.fdc_disc){
    32 		fprintf(stderr, "ERROR loading disc image 'discim'.\n");
    33 		state.fdc_disc = NULL;
    34 		return (0);
    35 	}else{
    36 		wd2797_load(&state.fdc_ctx, state.fdc_disc, 512, 10, 2, writeable);
    37 		fprintf(stderr, "Disc image loaded.\n");
    38 		return (1);
    39 	}
    40 }
    42 static int load_hd()
    43 {
    45 	state.hdc_disc0 = fopen("hd.img", "r+b");
    46 	if (!state.hdc_disc0){
    47 		fprintf(stderr, "ERROR loading disc image 'hd.img'.\n");
    48 		state.hdc_disc0 = NULL;
    49 		return (0);
    50 	}else{
    51 		wd2010_init(&state.hdc_ctx, state.hdc_disc0, 512, 17, 8);
    52 		fprintf(stderr, "Disc image loaded.\n");
    53 		return (1);
    54 	}
    55 }
    59 /**
    60  * @brief Set the pixel at (x, y) to the given value
    61  * @note The surface must be locked before calling this!
    62  * @param	surface		SDL surface upon which to draw
    63  * @param	x			X co-ordinate
    64  * @param	y			Y co-ordinate
    65  * @param	pixel		Pixel value (from SDL_MapRGB)
    66  */
    67 void putpixel(SDL_Surface *surface, int x, int y, Uint32 pixel)
    68 {
    69 	int bpp = surface->format->BytesPerPixel;
    70 	/* Here p is the address to the pixel we want to set */
    71 	Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;
    73 	switch (bpp) {
    74 		case 1:
    75 			*p = pixel;
    76 			break;
    78 		case 2:
    79 			*(Uint16 *)p = pixel;
    80 			break;
    82 		case 3:
    83 			if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
    84 				p[0] = (pixel >> 16) & 0xff;
    85 				p[1] = (pixel >> 8) & 0xff;
    86 				p[2] = pixel & 0xff;
    87 			}
    88 			else {
    89 				p[0] = pixel & 0xff;
    90 				p[1] = (pixel >> 8) & 0xff;
    91 				p[2] = (pixel >> 16) & 0xff;
    92 			}
    93 			break;
    95 		case 4:
    96 			*(Uint32 *)p = pixel;
    97 			break;
    99 		default:
   100 			break;           /* shouldn't happen, but avoids warnings */
   101 	} // switch
   102 }
   105 /**
   106  * @brief	Refresh the screen.
   107  * @param	surface		SDL surface upon which to draw.
   108  */
   109 void refreshScreen(SDL_Surface *s)
   110 {
   111 	// Lock the screen surface (if necessary)
   112 	if (SDL_MUSTLOCK(s)) {
   113 		if (SDL_LockSurface(s) < 0) {
   114 			fprintf(stderr, "ERROR: Unable to lock screen!\n");
   115 			exit(EXIT_FAILURE);
   116 		}
   117 	}
   119 	// Map the foreground and background colours
   120 	Uint32 fg = SDL_MapRGB(s->format, 0x00, 0xFF, 0x00);	// green foreground
   121 //	Uint32 fg = SDL_MapRGB(s->format, 0xFF, 0xC1, 0x06);	// amber foreground
   122 //	Uint32 fg = SDL_MapRGB(s->format, 0xFF, 0xFF, 0xFF);	// white foreground
   123 	Uint32 bg = SDL_MapRGB(s->format, 0x00, 0x00, 0x00);	// black background
   125 	// Refresh the 3B1 screen area first. TODO: only do this if VRAM has actually changed!
   126 	uint32_t vram_address = 0;
   127 	for (int y=0; y<348; y++) {
   128 		for (int x=0; x<720; x+=16) {	// 720 pixels, monochrome, packed into 16bit words
   129 			// Get the pixel
   130 			uint16_t val = RD16(state.vram, vram_address, sizeof(state.vram)-1);
   131 			vram_address += 2;
   132 			// Now copy it to the video buffer
   133 			for (int px=0; px<16; px++) {
   134 				if (val & 1)
   135 					putpixel(s, x+px, y, fg);
   136 				else
   137 					putpixel(s, x+px, y, bg);
   138 				val >>= 1;
   139 			}
   140 		}
   141 	}
   143 	// TODO: blit LEDs and status info
   145 	// Unlock the screen surface
   146 	if (SDL_MUSTLOCK(s)) {
   147 		SDL_UnlockSurface(s);
   148 	}
   150 	// Trigger a refresh -- TODO: partial refresh depending on whether we
   151 	// refreshed the screen area, status area, both, or none. Use SDL_UpdateRect() for this.
   152 	SDL_Flip(s);
   153 }
   155 /**
   156  * @brief	Handle events posted by SDL.
   157  */
   158 bool HandleSDLEvents(SDL_Surface *screen)
   159 {
   160 	SDL_Event event;
   161 	while (SDL_PollEvent(&event))
   162 	{
   163 		if ((event.type == SDL_KEYDOWN) || (event.type == SDL_KEYUP)) {
   164 			keyboard_event(&state.kbd, &event);
   165 		}
   167 		switch (event.type) {
   168 			case SDL_QUIT:
   169 				// Quit button tagged. Exit.
   170 				return true;
   171 			case SDL_KEYDOWN:
   172 				switch (event.key.keysym.sym) {
   173 					case SDLK_F11:
   174 						if (state.fdc_disc) {
   175 							wd2797_unload(&state.fdc_ctx);
   176 							fclose(state.fdc_disc);
   177 							state.fdc_disc = NULL;
   178 							fprintf(stderr, "Disc image unloaded.\n");
   179 						} else {
   180 							load_fd();
   181 						}
   182 						break;
   183 					case SDLK_F12:
   184 						if (event.key.keysym.mod & (KMOD_LALT | KMOD_RALT))
   185 							// ALT-F12 pressed; exit emulator
   186 							return true;
   187 						break;
   188 					default:
   189 						break;
   190 				}
   191 				break;
   192 			default:
   193 				break;
   194 		}
   195 	}
   197 	return false;
   198 }
   201 /****************************
   202  * blessed be thy main()...
   203  ****************************/
   205 int main(void)
   206 {
   207 	// copyright banner
   208 	printf("FreeBee: A Quick-and-Dirty AT&T 3B1 Emulator. Version %s, %s mode.\n", VER_FULLSTR, VER_BUILD_TYPE);
   209 	printf("Copyright (C) 2010 P. A. Pemberton. All rights reserved.\nLicensed under the Apache License Version 2.0.\n");
   210 	printf("Musashi M680x0 emulator engine developed by Karl Stenerud <kstenerud@gmail.com>\n");
   211 	printf("Built %s by %s@%s.\n", VER_COMPILE_DATETIME, VER_COMPILE_BY, VER_COMPILE_HOST);
   212 	printf("Compiler: %s\n", VER_COMPILER);
   213 	printf("CFLAGS: %s\n", VER_CFLAGS);
   214 	printf("\n");
   216 	// set up system state
   217 	// 512K of RAM
   218 	int i;
   219 	if ((i = state_init(2048*1024, 2048*1024)) != STATE_E_OK) {
   220 		fprintf(stderr, "ERROR: Emulator initialisation failed. Error code %d.\n", i);
   221 		return i;
   222 	}
   224 	// set up musashi and reset the CPU
   225 	m68k_set_cpu_type(M68K_CPU_TYPE_68010);
   226 	m68k_pulse_reset();
   228 	// Set up SDL
   229 	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) == -1) {
   230 		printf("Could not initialise SDL: %s.\n", SDL_GetError());
   231 		exit(EXIT_FAILURE);
   232 	}
   234 	// Make sure SDL cleans up after itself
   235 	atexit(SDL_Quit);
   237 	// Set up the video display
   238 	SDL_Surface *screen = NULL;
   239 	if ((screen = SDL_SetVideoMode(720, 384, 8, SDL_SWSURFACE | SDL_ANYFORMAT)) == NULL) {
   240 		printf("Could not find a suitable video mode: %s.\n", SDL_GetError());
   241 		exit(EXIT_FAILURE);
   242 	}
   243 	printf("Set %dx%d at %d bits-per-pixel mode\n\n", screen->w, screen->h, screen->format->BitsPerPixel);
   244 	SDL_WM_SetCaption("FreeBee 3B1 emulator", "FreeBee");
   246 	// Load a disc image
   247 	load_fd();
   249 	load_hd();
   251 	/***
   252 	 * The 3B1 CPU runs at 10MHz, with DMA running at 1MHz and video refreshing at
   253 	 * around 60Hz (???), with a 60Hz periodic interrupt.
   254 	 */
   255 	const uint32_t SYSTEM_CLOCK = 10e6; // Hz
   256 	const uint32_t TIMESLOT_FREQUENCY = 1000;//240;	// Hz
   257 	const uint32_t MILLISECS_PER_TIMESLOT = 1e3 / TIMESLOT_FREQUENCY;
   258 	const uint32_t CLOCKS_PER_60HZ = (SYSTEM_CLOCK / 60);
   259 	uint32_t next_timeslot = SDL_GetTicks() + MILLISECS_PER_TIMESLOT;
   260 	uint32_t clock_cycles = 0, tmp;
   261 	bool exitEmu = false;
   263 	/*bool lastirq_fdc = false;*/
   264 	for (;;) {
   265 		// Run the CPU for however many cycles we need to. CPU core clock is
   266 		// 10MHz, and we're running at 240Hz/timeslot. Thus: 10e6/240 or
   267 		// 41667 cycles per timeslot.
   268 		tmp = m68k_execute(SYSTEM_CLOCK/TIMESLOT_FREQUENCY);
   269 		clock_cycles += tmp;
   271 		// Run the DMA engine
   272 		if (state.dmaen) {
   273 			// DMA ready to go -- so do it.
   274 			size_t num = 0;
   275 			while (state.dma_count < 0x4000) {
   276 				uint16_t d = 0;
   277 				// num tells us how many words we've copied. If this is greater than the per-timeslot DMA maximum, bail out!
   278 				if (num > (1e6/TIMESLOT_FREQUENCY)) break;
   280 				// Evidently we have more words to copy. Copy them.
   281 				if (state.fd_selected){
   282 					if (!wd2797_get_drq(&state.fdc_ctx)) {
   283 						// Bail out, no data available. Try again later.
   284 						break;
   285 					}
   286 				}else if (state.hd_selected){
   287 					if (!wd2010_get_drq(&state.hdc_ctx)) {
   288 						// Bail out, no data available. Try again later.
   289 						break;
   290 					}
   291 				}else{
   292 					printf("ERROR: DMA attempt with no drive selected!\n");
   293 				}
   294 				if (!access_check_dma(state.dma_reading)) {
   295 					break;
   296 				}
   297 				uint32_t newAddr;
   298 				// Map logical address to a physical RAM address
   299 				newAddr = mapAddr(state.dma_address, !state.dma_reading);
   301 				if (!state.dma_reading) {
   302 					// Data available. Get it from the FDC or HDC.
   303 					if (state.fd_selected) {
   304 						d = wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA);
   305 						d <<= 8;
   306 						d += wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA);
   307 					}else if (state.hd_selected) {
   308 						d = wd2010_read_data(&state.hdc_ctx);
   309 						d <<= 8;
   310 						d += wd2010_read_data(&state.hdc_ctx);
   311 					}
   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 				} else {
   318 					// Data write to FDC or HDC.
   320 					// Get the data from RAM
   321 					if (newAddr <= 0x1fffff) {
   322 						d = RD16(state.base_ram, newAddr, state.base_ram_size - 1);
   323 					} else {
   324 						if (newAddr <= (state.exp_ram_size + 0x200000 - 1))
   325 							d = RD16(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1);
   326 						else
   327 							d = 0xffff;
   328 					}
   330 					// Send the data to the FDD or HDD
   331 					if (state.fd_selected){
   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 					}else if (state.hd_selected){
   335 						wd2010_write_data(&state.hdc_ctx, (d >> 8));
   336 						wd2010_write_data(&state.hdc_ctx, (d & 0xff));
   337 					}
   338 				}
   340 				// Increment DMA address
   341 				state.dma_address+=2;
   342 				// Increment number of words transferred
   343 				num++; state.dma_count++;
   344 			}
   346 			// Turn off DMA engine if we finished this cycle
   347 			if (state.dma_count >= 0x4000) {
   348 				// FIXME? apparently this isn't required... or is it?
   349 				state.dma_count = 0x3fff;
   350 				/*state.dmaen = false;*/
   351 			}
   352 		}else if (wd2010_get_drq(&state.hdc_ctx)){
   353 			wd2010_dma_miss(&state.hdc_ctx);
   354 		}else if (wd2797_get_drq(&state.fdc_ctx)){
   355 			wd2797_dma_miss(&state.fdc_ctx);
   356 		}
   359 		// Any interrupts? --> TODO: masking
   360 /*		if (!lastirq_fdc) {
   361 			if (wd2797_get_irq(&state.fdc_ctx)) {
   362 				lastirq_fdc = true;
   363 				m68k_set_irq(2);
   364 			}
   365 		}
   366 */
   367 		if (wd2797_get_irq(&state.fdc_ctx) || wd2010_get_irq(&state.hdc_ctx)) {
   368 			m68k_set_irq(2);
   369 		}else if (keyboard_get_irq(&state.kbd)) {
   370 			m68k_set_irq(3);
   371 		} else {
   372 //			if (!state.timer_asserted){
   373 				m68k_set_irq(0);
   374 //			}
   375 		}
   377 		// Is it time to run the 60Hz periodic interrupt yet?
   378 		if (clock_cycles > CLOCKS_PER_60HZ) {
   379 			// Refresh the screen
   380 			refreshScreen(screen);
   381 			if (state.timer_enabled){
   382 				m68k_set_irq(6);
   383 				state.timer_asserted = true;
   384 			}
   385 			// scan the keyboard
   386 			keyboard_scan(&state.kbd);
   387 			// decrement clock cycle counter, we've handled the intr.
   388 			clock_cycles -= CLOCKS_PER_60HZ;
   389 		}
   391 		// handle SDL events -- returns true if we need to exit
   392 		if (HandleSDLEvents(screen))
   393 			exitEmu = true;
   395 		// make sure frame rate is equal to real time
   396 		uint32_t now = SDL_GetTicks();
   397 		if (now < next_timeslot) {
   398 			// timeslot finished early -- eat up some time
   399 			SDL_Delay(next_timeslot - now);
   400 		} else {
   401 			// timeslot finished late -- skip ahead to gain time
   402 			// TODO: if this happens a lot, we should let the user know
   403 			// that their PC might not be fast enough...
   404 			next_timeslot = now;
   405 		}
   406 		// advance to the next timeslot
   407 		next_timeslot += MILLISECS_PER_TIMESLOT;
   409 		// if we've been asked to exit the emulator, then do so.
   410 		if (exitEmu) break;
   411 	}
   413 	// Release the disc image
   414 	wd2797_unload(&state.fdc_ctx);
   415 	fclose(state.fdc_disc);
   417 	return 0;
   418 }