src/main.c

Wed, 13 Mar 2013 00:43:25 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Wed, 13 Mar 2013 00:43:25 +0000
changeset 134
b826697f411a
parent 127
4c03f6433d0d
child 135
159f937af10d
child 152
d61e13d6e2a5
permissions
-rw-r--r--

[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 }