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