Tue, 15 Nov 2011 10:12:37 +0000
[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 | } |