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