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