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

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