src/main.c

Mon, 06 Dec 2010 01:43:04 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Mon, 06 Dec 2010 01:43:04 +0000
changeset 54
57c6ef81ae81
parent 53
e1693c4b8a0c
child 55
ba6b8e570062
child 76
2ef98ea1e944
permissions
-rw-r--r--

fix side-select bug in WDC FDC driver, was causing all reads to occur on side0... now the Loader boots!

Loader will boot, but immediately gives up on the floppy drive... Not sure why.

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@47 126 switch (event.type) {
philpem@47 127 case SDL_QUIT:
philpem@47 128 // Quit button tagged. Exit.
philpem@47 129 return true;
philpem@47 130 case SDL_KEYDOWN:
philpem@47 131 switch (event.key.keysym.sym) {
philpem@48 132 case SDLK_F12:
philpem@47 133 if (event.key.keysym.mod & (KMOD_LALT | KMOD_RALT))
philpem@48 134 // ALT-F12 pressed; exit emulator
philpem@47 135 return true;
philpem@47 136 break;
philpem@47 137 default:
philpem@47 138 break;
philpem@47 139 }
philpem@47 140 break;
philpem@47 141 default:
philpem@47 142 break;
philpem@47 143 }
philpem@47 144 }
philpem@47 145
philpem@47 146 return false;
philpem@47 147 }
philpem@47 148
philpem@47 149
philpem@27 150 /****************************
philpem@27 151 * blessed be thy main()...
philpem@27 152 ****************************/
philpem@27 153
philpem@0 154 int main(void)
philpem@0 155 {
philpem@7 156 // copyright banner
philpem@16 157 printf("FreeBee: A Quick-and-Dirty AT&T 3B1 Emulator. Version %s, %s mode.\n", VER_FULLSTR, VER_BUILD_TYPE);
philpem@17 158 printf("Copyright (C) 2010 P. A. Pemberton. All rights reserved.\nLicensed under the Apache License Version 2.0.\n");
philpem@17 159 printf("Musashi M680x0 emulator engine developed by Karl Stenerud <kstenerud@gmail.com>\n");
philpem@16 160 printf("Built %s by %s@%s.\n", VER_COMPILE_DATETIME, VER_COMPILE_BY, VER_COMPILE_HOST);
philpem@16 161 printf("Compiler: %s\n", VER_COMPILER);
philpem@16 162 printf("CFLAGS: %s\n", VER_CFLAGS);
philpem@17 163 printf("\n");
philpem@7 164
philpem@7 165 // set up system state
philpem@7 166 // 512K of RAM
philpem@18 167 state_init(512*1024);
philpem@7 168
philpem@20 169 // set up musashi and reset the CPU
philpem@7 170 m68k_set_cpu_type(M68K_CPU_TYPE_68010);
philpem@7 171 m68k_pulse_reset();
philpem@9 172
philpem@28 173 // Set up SDL
philpem@20 174 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) == -1) {
philpem@20 175 printf("Could not initialise SDL: %s.\n", SDL_GetError());
philpem@28 176 exit(EXIT_FAILURE);
philpem@20 177 }
philpem@7 178
philpem@28 179 // Make sure SDL cleans up after itself
philpem@28 180 atexit(SDL_Quit);
philpem@28 181
philpem@28 182 // Set up the video display
philpem@28 183 SDL_Surface *screen = NULL;
philpem@28 184 if ((screen = SDL_SetVideoMode(720, 384, 8, SDL_SWSURFACE | SDL_ANYFORMAT)) == NULL) {
philpem@28 185 printf("Could not find a suitable video mode: %s.\n", SDL_GetError());
philpem@28 186 exit(EXIT_FAILURE);
philpem@28 187 }
philpem@32 188 printf("Set %dx%d at %d bits-per-pixel mode\n\n", screen->w, screen->h, screen->format->BitsPerPixel);
philpem@28 189 SDL_WM_SetCaption("FreeBee 3B1 emulator", "FreeBee");
philpem@28 190
philpem@52 191 // Load a disc image
philpem@52 192 FILE *disc = fopen("discim", "rb");
philpem@52 193 wd2797_load(&state.fdc_ctx, disc, 512, 10, 2);
philpem@52 194
philpem@20 195 /***
philpem@20 196 * The 3B1 CPU runs at 10MHz, with DMA running at 1MHz and video refreshing at
philpem@20 197 * around 60Hz (???), with a 60Hz periodic interrupt.
philpem@20 198 */
philpem@20 199 const uint32_t TIMESLOT_FREQUENCY = 240; // Hz
philpem@20 200 const uint32_t MILLISECS_PER_TIMESLOT = 1e3 / TIMESLOT_FREQUENCY;
philpem@20 201 const uint32_t CLOCKS_PER_60HZ = (10e6 / 60);
philpem@20 202 uint32_t next_timeslot = SDL_GetTicks() + MILLISECS_PER_TIMESLOT;
philpem@20 203 uint32_t clock_cycles = 0;
philpem@16 204 bool exitEmu = false;
philpem@16 205 for (;;) {
philpem@20 206 // Run the CPU for however many cycles we need to. CPU core clock is
philpem@20 207 // 10MHz, and we're running at 240Hz/timeslot. Thus: 10e6/240 or
philpem@20 208 // 41667 cycles per timeslot.
philpem@20 209 clock_cycles += m68k_execute(10e6/TIMESLOT_FREQUENCY);
philpem@20 210
philpem@53 211 // Run the DMA engine
philpem@53 212 //
philpem@53 213 if (state.dmaen) { //((state.dma_count < 0x3fff) && state.dmaen) {
philpem@53 214 printf("DMA: copy addr=%08X count=%08X idmarw=%d dmarw=%d\n", state.dma_address, state.dma_count, state.idmarw, state.dma_reading);
philpem@53 215 if (state.dmaenb) {
philpem@53 216 state.dmaenb = false;
philpem@53 217 // state.dma_address++;
philpem@53 218 state.dma_count++;
philpem@53 219 }
philpem@53 220 // DMA ready to go -- so do it.
philpem@53 221 size_t num = 0;
philpem@53 222 while (state.dma_count < 0x4000) {
philpem@53 223 uint16_t d = 0;
philpem@53 224
philpem@53 225 // num tells us how many words we've copied. If this is greater than the per-timeslot DMA maximum, bail out!
philpem@53 226 if (num > (1e6/TIMESLOT_FREQUENCY)) break;
philpem@53 227
philpem@53 228 // Evidently we have more words to copy. Copy them.
philpem@53 229 if (!wd2797_get_drq(&state.fdc_ctx)) {
philpem@53 230 printf("\tDMABAIL: no data! dmac=%04X dmaa=%04X\n", state.dma_count, state.dma_address);
philpem@53 231 // Bail out, no data available. Try again later.
philpem@53 232 // TODO: handle HDD controller too
philpem@53 233 break;
philpem@53 234 }
philpem@53 235
philpem@53 236 if (!state.dma_reading) {
philpem@53 237 // Data available. Get it from the FDC.
philpem@53 238 d = wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA);
philpem@53 239 d <<= 8;
philpem@53 240 d += wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA);
philpem@53 241
philpem@53 242 // TODO: FIXME: if we get a pagefault, it NEEDS to be tagged as 'peripheral sourced'... this is a HACK!
philpem@53 243 m68k_write_memory_16(state.dma_address << 1, d);
philpem@53 244 } else {
philpem@53 245 // Data write to FDC
philpem@53 246 // TODO: FIXME: if we get a pagefault, it NEEDS to be tagged as 'peripheral sourced'... this is a HACK!
philpem@53 247 d = m68k_read_memory_16(state.dma_address << 1);
philpem@53 248
philpem@53 249 wd2797_write_reg(&state.fdc_ctx, WD2797_REG_DATA, (d >> 8));
philpem@53 250 wd2797_write_reg(&state.fdc_ctx, WD2797_REG_DATA, (d & 0xff));
philpem@53 251 }
philpem@53 252
philpem@53 253 // Increment DMA address
philpem@53 254 state.dma_address++;
philpem@53 255 // Increment number of words transferred
philpem@53 256 num++; state.dma_count++;
philpem@53 257 }
philpem@53 258
philpem@53 259 // Turn off DMA engine if we finished this cycle
philpem@53 260 if (state.dma_count >= 0x4000) {
philpem@53 261 printf("\tDMATRAN: transfer complete! dmaa=%06X, dmac=%04X\n", state.dma_address, state.dma_count);
philpem@53 262 state.dma_count = 0;
philpem@53 263 state.dmaen = false;
philpem@53 264 }
philpem@53 265 }
philpem@53 266
philpem@53 267 // Any interrupts?
philpem@53 268 if (wd2797_get_irq(&state.fdc_ctx)) {
philpem@53 269 m68k_set_irq(2);
philpem@53 270 } else {
philpem@53 271 m68k_set_irq(0);
philpem@53 272 }
philpem@16 273
philpem@20 274 // Is it time to run the 60Hz periodic interrupt yet?
philpem@20 275 if (clock_cycles > CLOCKS_PER_60HZ) {
philpem@41 276 // Refresh the screen
philpem@41 277 refreshScreen(screen);
philpem@20 278 // TODO: trigger periodic interrupt (if enabled)
philpem@20 279 // decrement clock cycle counter, we've handled the intr.
philpem@20 280 clock_cycles -= CLOCKS_PER_60HZ;
philpem@16 281 }
philpem@16 282
philpem@47 283 // handle SDL events -- returns true if we need to exit
philpem@47 284 if (HandleSDLEvents(screen))
philpem@47 285 exitEmu = true;
philpem@47 286
philpem@20 287 // make sure frame rate is equal to real time
philpem@20 288 uint32_t now = SDL_GetTicks();
philpem@20 289 if (now < next_timeslot) {
philpem@20 290 // timeslot finished early -- eat up some time
philpem@20 291 SDL_Delay(next_timeslot - now);
philpem@20 292 } else {
philpem@20 293 // timeslot finished late -- skip ahead to gain time
philpem@20 294 // TODO: if this happens a lot, we should let the user know
philpem@20 295 // that their PC might not be fast enough...
philpem@20 296 next_timeslot = now;
philpem@20 297 }
philpem@20 298 // advance to the next timeslot
philpem@20 299 next_timeslot += MILLISECS_PER_TIMESLOT;
philpem@20 300
philpem@20 301 // if we've been asked to exit the emulator, then do so.
philpem@16 302 if (exitEmu) break;
philpem@16 303 }
philpem@7 304
philpem@52 305 // Release the disc image
philpem@52 306 wd2797_unload(&state.fdc_ctx);
philpem@52 307 fclose(disc);
philpem@52 308
philpem@0 309 return 0;
philpem@0 310 }