src/main.c

Mon, 14 Jan 2013 09:22:12 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Mon, 14 Jan 2013 09:22:12 +0000
changeset 118
feee84e0b3bf
parent 112
a392eb8f9806
child 120
df40e6668a46
permissions
-rw-r--r--

More bus error fixes for FreeBee

I have fixed two more bus error handling bugs in FreeBee. First, the CPU core was executing the instruction regardless of whether a bus error occurs when fetching the opcode (which caused it to execute a bogus instruction in such cases). The other one was related to one of my previous fixes - the jump to the bus error vector was at the beginning of the main loop, so it wouldn't be called immediately after the bus error occurred if the timeslot expired, causing the return address to be off.

With these fixes, Unix now runs enough to get into userspace and run the install script (it is also possible to break out and get a shell prompt). However, many commands segfault semi-randomly (or more specifically, it seems that some child processes forked by the shell might be segfaulting before they can exec the command program), so installing the system isn't possible yet. I am not sure exactly what the bug is, but it seems to be related to some function in the shell returning null when the code calling it is assuming that it won't. What the function is, or why it is returning null, I'm not sure (the shell is built without the shared libc and is stripped, making identifying the function harder). I suspect that the function might be in libc, but that is hard to tell.

Author: Andrew Warkentin <andreww591 gmail com>

     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, 16, 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(512*1024, 512*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 }