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