src/memory.c

Tue, 15 Nov 2011 10:12:37 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Tue, 15 Nov 2011 10:12:37 +0000
changeset 109
2f8afb9e5baa
parent 108
5f7faf5ecbf4
child 112
a392eb8f9806
permissions
-rw-r--r--

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

philpem@40 1 #include <stdio.h>
philpem@40 2 #include <stdlib.h>
philpem@40 3 #include <stdint.h>
philpem@40 4 #include <stdbool.h>
philpem@59 5 #include <assert.h>
philpem@40 6 #include "musashi/m68k.h"
philpem@40 7 #include "state.h"
philpem@100 8 #include "utils.h"
philpem@40 9 #include "memory.h"
philpem@40 10
philpem@40 11 /******************
philpem@40 12 * Memory mapping
philpem@40 13 ******************/
philpem@40 14
philpem@40 15 #define MAPRAM(addr) (((uint16_t)state.map[addr*2] << 8) + ((uint16_t)state.map[(addr*2)+1]))
philpem@40 16
philpem@59 17 uint32_t mapAddr(uint32_t addr, bool writing)/*{{{*/
philpem@40 18 {
philpem@40 19 if (addr < 0x400000) {
philpem@40 20 // RAM access. Check against the Map RAM
philpem@40 21 // Start by getting the original page address
philpem@40 22 uint16_t page = (addr >> 12) & 0x3FF;
philpem@40 23
philpem@40 24 // Look it up in the map RAM and get the physical page address
philpem@40 25 uint32_t new_page_addr = MAPRAM(page) & 0x3FF;
philpem@40 26
philpem@40 27 // Update the Page Status bits
philpem@40 28 uint8_t pagebits = (MAPRAM(page) >> 13) & 0x03;
philpem@100 29 // Pagebits --
philpem@100 30 // 0 = not present
philpem@100 31 // 1 = present but not accessed
philpem@100 32 // 2 = present, accessed (read from)
philpem@100 33 // 3 = present, dirty (written to)
philpem@100 34 switch (pagebits) {
philpem@100 35 case 0:
philpem@100 36 // Page not present
philpem@100 37 // This should cause a page fault
philpem@100 38 LOGS("Whoa! Pagebit update, when the page is not present!");
philpem@100 39 break;
philpem@100 40
philpem@100 41 case 1:
philpem@100 42 // Page present -- first access
philpem@104 43 state.map[page*2] &= 0x9F; // turn off "present" bit (but not write enable!)
philpem@100 44 if (writing)
philpem@100 45 state.map[page*2] |= 0x60; // Page written to (dirty)
philpem@100 46 else
philpem@100 47 state.map[page*2] |= 0x40; // Page accessed but not written
philpem@100 48 break;
philpem@100 49
philpem@100 50 case 2:
philpem@100 51 case 3:
philpem@100 52 // Page present, 2nd or later access
philpem@100 53 if (writing)
philpem@100 54 state.map[page*2] |= 0x60; // Page written to (dirty)
philpem@100 55 break;
philpem@40 56 }
philpem@40 57
philpem@40 58 // Return the address with the new physical page spliced in
philpem@40 59 return (new_page_addr << 12) + (addr & 0xFFF);
philpem@40 60 } else {
philpem@40 61 // I/O, VRAM or MapRAM space; no mapping is performed or required
philpem@40 62 // TODO: assert here?
philpem@40 63 return addr;
philpem@40 64 }
philpem@59 65 }/*}}}*/
philpem@40 66
philpem@59 67 MEM_STATUS checkMemoryAccess(uint32_t addr, bool writing)/*{{{*/
philpem@40 68 {
philpem@104 69 // Get the page bits for this page.
philpem@104 70 uint16_t page = (addr >> 12) & 0x3FF;
philpem@104 71 uint8_t pagebits = (MAPRAM(page) >> 13) & 0x07;
philpem@104 72
philpem@104 73 // Check page is present (but only for RAM zone)
philpem@104 74 if ((addr < 0x400000) && ((pagebits & 0x03) == 0)) {
philpem@104 75 LOG("Page not mapped in: addr %08X, page %04X, mapbits %04X", addr, page, MAPRAM(page));
philpem@104 76 return MEM_PAGEFAULT;
philpem@104 77 }
philpem@104 78
philpem@40 79 // Are we in Supervisor mode?
philpem@40 80 if (m68k_get_reg(NULL, M68K_REG_SR) & 0x2000)
philpem@40 81 // Yes. We can do anything we like.
philpem@40 82 return MEM_ALLOWED;
philpem@40 83
philpem@40 84 // If we're here, then we must be in User mode.
philpem@40 85 // Check that the user didn't access memory outside of the RAM area
philpem@106 86 if (addr >= 0x400000) {
philpem@106 87 LOGS("User accessed privileged memory");
philpem@40 88 return MEM_UIE;
philpem@106 89 }
philpem@40 90
philpem@40 91 // User attempt to access the kernel
philpem@40 92 // A19, A20, A21, A22 low (kernel access): RAM addr before paging; not in Supervisor mode
philpem@106 93 if (((addr >> 19) & 0x0F) == 0) {
philpem@106 94 LOGS("Attempt by user code to access kernel space");
philpem@40 95 return MEM_KERNEL;
philpem@106 96 }
philpem@40 97
philpem@40 98 // Check page is write enabled
philpem@106 99 if (writing && ((pagebits & 0x04) == 0)) {
philpem@106 100 LOG("Page not write enabled: inaddr %08X, page %04X, mapram %04X [%02X %02X], pagebits %d",
philpem@106 101 addr, page, MAPRAM(page), state.map[page*2], state.map[(page*2)+1], pagebits);
philpem@40 102 return MEM_PAGE_NO_WE;
philpem@106 103 }
philpem@40 104
philpem@40 105 // Page access allowed.
philpem@40 106 return MEM_ALLOWED;
philpem@59 107 }/*}}}*/
philpem@40 108
philpem@40 109 #undef MAPRAM
philpem@40 110
philpem@40 111
philpem@40 112 /********************************************************
philpem@40 113 * m68k memory read/write support functions for Musashi
philpem@40 114 ********************************************************/
philpem@40 115
philpem@40 116 /**
philpem@40 117 * @brief Check memory access permissions for a write operation.
philpem@40 118 * @note This used to be a single macro (merged with ACCESS_CHECK_RD), but
philpem@40 119 * gcc throws warnings when you have a return-with-value in a void
philpem@40 120 * function, even if the return-with-value is completely unreachable.
philpem@40 121 * Similarly it doesn't like it if you have a return without a value
philpem@40 122 * in a non-void function, even if it's impossible to ever reach the
philpem@40 123 * return-with-no-value. UGH!
philpem@40 124 */
philpem@59 125 /*{{{ macro: ACCESS_CHECK_WR(address, bits)*/
philpem@59 126 #define ACCESS_CHECK_WR(address, bits) \
philpem@59 127 do { \
philpem@40 128 bool fault = false; \
philpem@103 129 MEM_STATUS st; \
philpem@103 130 switch (st = checkMemoryAccess(address, true)) { \
philpem@40 131 case MEM_ALLOWED: \
philpem@40 132 /* Access allowed */ \
philpem@40 133 break; \
philpem@40 134 case MEM_PAGEFAULT: \
philpem@40 135 /* Page fault */ \
philpem@44 136 state.genstat = 0x8BFF | (state.pie ? 0x0400 : 0); \
philpem@40 137 fault = true; \
philpem@40 138 break; \
philpem@40 139 case MEM_UIE: \
philpem@40 140 /* User access to memory above 4MB */ \
philpem@44 141 state.genstat = 0x9AFF | (state.pie ? 0x0400 : 0); \
philpem@40 142 fault = true; \
philpem@40 143 break; \
philpem@40 144 case MEM_KERNEL: \
philpem@40 145 case MEM_PAGE_NO_WE: \
philpem@40 146 /* kernel access or page not write enabled */ \
philpem@68 147 /* FIXME: which regs need setting? */ \
philpem@40 148 fault = true; \
philpem@40 149 break; \
philpem@40 150 } \
philpem@40 151 \
philpem@40 152 if (fault) { \
philpem@40 153 if (bits >= 16) \
philpem@68 154 state.bsr0 = 0x7C00; \
philpem@40 155 else \
philpem@108 156 state.bsr0 = (address & 1) ? 0x7E00 : 0x7D00; \
philpem@40 157 state.bsr0 |= (address >> 16); \
philpem@40 158 state.bsr1 = address & 0xffff; \
philpem@103 159 LOG("Bus Error while writing, addr %08X, statcode %d", address, st); \
philpem@103 160 if (state.ee) m68k_pulse_bus_error(); \
philpem@40 161 return; \
philpem@40 162 } \
philpem@70 163 } while (0)
philpem@59 164 /*}}}*/
philpem@40 165
philpem@40 166 /**
philpem@40 167 * @brief Check memory access permissions for a read operation.
philpem@40 168 * @note This used to be a single macro (merged with ACCESS_CHECK_WR), but
philpem@40 169 * gcc throws warnings when you have a return-with-value in a void
philpem@40 170 * function, even if the return-with-value is completely unreachable.
philpem@40 171 * Similarly it doesn't like it if you have a return without a value
philpem@40 172 * in a non-void function, even if it's impossible to ever reach the
philpem@40 173 * return-with-no-value. UGH!
philpem@40 174 */
philpem@59 175 /*{{{ macro: ACCESS_CHECK_RD(address, bits)*/
philpem@59 176 #define ACCESS_CHECK_RD(address, bits) \
philpem@59 177 do { \
philpem@40 178 bool fault = false; \
philpem@103 179 MEM_STATUS st; \
philpem@103 180 switch (st = checkMemoryAccess(address, false)) { \
philpem@40 181 case MEM_ALLOWED: \
philpem@40 182 /* Access allowed */ \
philpem@40 183 break; \
philpem@40 184 case MEM_PAGEFAULT: \
philpem@40 185 /* Page fault */ \
philpem@44 186 state.genstat = 0xCBFF | (state.pie ? 0x0400 : 0); \
philpem@40 187 fault = true; \
philpem@40 188 break; \
philpem@40 189 case MEM_UIE: \
philpem@40 190 /* User access to memory above 4MB */ \
philpem@44 191 state.genstat = 0xDAFF | (state.pie ? 0x0400 : 0); \
philpem@40 192 fault = true; \
philpem@40 193 break; \
philpem@40 194 case MEM_KERNEL: \
philpem@40 195 case MEM_PAGE_NO_WE: \
philpem@40 196 /* kernel access or page not write enabled */ \
philpem@68 197 /* FIXME: which regs need setting? */ \
philpem@40 198 fault = true; \
philpem@40 199 break; \
philpem@40 200 } \
philpem@40 201 \
philpem@40 202 if (fault) { \
philpem@40 203 if (bits >= 16) \
philpem@68 204 state.bsr0 = 0x7C00; \
philpem@40 205 else \
philpem@108 206 state.bsr0 = (address & 1) ? 0x7E00 : 0x7D00; \
philpem@40 207 state.bsr0 |= (address >> 16); \
philpem@40 208 state.bsr1 = address & 0xffff; \
philpem@103 209 LOG("Bus Error while reading, addr %08X, statcode %d", address, st); \
philpem@103 210 if (state.ee) m68k_pulse_bus_error(); \
philpem@40 211 return 0xFFFFFFFF; \
philpem@40 212 } \
philpem@70 213 } while (0)
philpem@59 214 /*}}}*/
philpem@40 215
philpem@40 216 // Logging macros
philpem@59 217 #define LOG_NOT_HANDLED_R(bits) \
philpem@64 218 if (!handled) printf("unhandled read%02d, addr=0x%08X\n", bits, address);
philpem@40 219
philpem@59 220 #define LOG_NOT_HANDLED_W(bits) \
philpem@64 221 if (!handled) printf("unhandled write%02d, addr=0x%08X, data=0x%08X\n", bits, address, data);
philpem@59 222
philpem@59 223 /********************************************************
philpem@59 224 * I/O read/write functions
philpem@59 225 ********************************************************/
philpem@40 226
philpem@40 227 /**
philpem@59 228 * Issue a warning if a read operation is made with an invalid size
philpem@40 229 */
philpem@66 230 inline static void ENFORCE_SIZE(int bits, uint32_t address, bool read, int allowed, char *regname)
philpem@40 231 {
philpem@59 232 assert((bits == 8) || (bits == 16) || (bits == 32));
philpem@59 233 if ((bits & allowed) == 0) {
philpem@66 234 printf("WARNING: %s 0x%08X (%s) with invalid size %d!\n", read ? "read from" : "write to", address, regname, bits);
philpem@59 235 }
philpem@59 236 }
philpem@59 237
philpem@66 238 inline static void ENFORCE_SIZE_R(int bits, uint32_t address, int allowed, char *regname)
philpem@40 239 {
philpem@66 240 ENFORCE_SIZE(bits, address, true, allowed, regname);
philpem@66 241 }
philpem@66 242
philpem@66 243 inline static void ENFORCE_SIZE_W(int bits, uint32_t address, int allowed, char *regname)
philpem@66 244 {
philpem@66 245 ENFORCE_SIZE(bits, address, false, allowed, regname);
philpem@66 246 }
philpem@66 247
philpem@59 248 void IoWrite(uint32_t address, uint32_t data, int bits)/*{{{*/
philpem@59 249 {
philpem@40 250 bool handled = false;
philpem@40 251
philpem@59 252 if ((address >= 0x400000) && (address <= 0x7FFFFF)) {
philpem@40 253 // I/O register space, zone A
philpem@40 254 switch (address & 0x0F0000) {
philpem@40 255 case 0x010000: // General Status Register
philpem@59 256 if (bits == 16)
philpem@59 257 state.genstat = (data & 0xffff);
philpem@59 258 else if (bits == 8) {
philpem@59 259 if (address & 0)
philpem@59 260 state.genstat = data;
philpem@59 261 else
philpem@59 262 state.genstat = data << 8;
philpem@59 263 }
philpem@40 264 handled = true;
philpem@40 265 break;
philpem@40 266 case 0x030000: // Bus Status Register 0
philpem@40 267 break;
philpem@40 268 case 0x040000: // Bus Status Register 1
philpem@40 269 break;
philpem@40 270 case 0x050000: // Phone status
philpem@40 271 break;
philpem@40 272 case 0x060000: // DMA Count
philpem@66 273 ENFORCE_SIZE_W(bits, address, 16, "DMACOUNT");
philpem@59 274 state.dma_count = (data & 0x3FFF);
philpem@59 275 state.idmarw = ((data & 0x4000) == 0x4000);
philpem@59 276 state.dmaen = ((data & 0x8000) == 0x8000);
philpem@59 277 // This handles the "dummy DMA transfer" mentioned in the docs
philpem@59 278 // TODO: access check, peripheral access
philpem@59 279 if (!state.idmarw)
philpem@60 280 WR32(state.base_ram, mapAddr(address, true), state.base_ram_size - 1, 0xDEAD);
philpem@59 281 state.dma_count++;
philpem@53 282 handled = true;
philpem@40 283 break;
philpem@40 284 case 0x070000: // Line Printer Status Register
philpem@40 285 break;
philpem@40 286 case 0x080000: // Real Time Clock
philpem@40 287 break;
philpem@40 288 case 0x090000: // Phone registers
philpem@40 289 switch (address & 0x0FF000) {
philpem@40 290 case 0x090000: // Handset relay
philpem@40 291 case 0x098000:
philpem@40 292 break;
philpem@40 293 case 0x091000: // Line select 2
philpem@40 294 case 0x099000:
philpem@40 295 break;
philpem@40 296 case 0x092000: // Hook relay 1
philpem@40 297 case 0x09A000:
philpem@40 298 break;
philpem@40 299 case 0x093000: // Hook relay 2
philpem@40 300 case 0x09B000:
philpem@40 301 break;
philpem@40 302 case 0x094000: // Line 1 hold
philpem@40 303 case 0x09C000:
philpem@40 304 break;
philpem@40 305 case 0x095000: // Line 2 hold
philpem@40 306 case 0x09D000:
philpem@40 307 break;
philpem@40 308 case 0x096000: // Line 1 A-lead
philpem@40 309 case 0x09E000:
philpem@40 310 break;
philpem@40 311 case 0x097000: // Line 2 A-lead
philpem@40 312 case 0x09F000:
philpem@40 313 break;
philpem@40 314 }
philpem@40 315 break;
philpem@59 316 case 0x0A0000: // Miscellaneous Control Register
philpem@66 317 ENFORCE_SIZE_W(bits, address, 16, "MISCCON");
philpem@59 318 // TODO: handle the ctrl bits properly
philpem@59 319 // TODO: &0x8000 --> dismiss 60hz intr
philpem@97 320 if (data & 0x8000){
philpem@97 321 state.timer_enabled = 1;
philpem@97 322 }else{
philpem@97 323 state.timer_enabled = 0;
philpem@97 324 state.timer_asserted = 0;
philpem@97 325 }
philpem@59 326 state.dma_reading = (data & 0x4000);
philpem@72 327 if (state.leds != ((~data & 0xF00) >> 8)) {
philpem@72 328 state.leds = (~data & 0xF00) >> 8;
philpem@72 329 printf("LEDs: %s %s %s %s\n",
philpem@72 330 (state.leds & 8) ? "R" : "-",
philpem@72 331 (state.leds & 4) ? "G" : "-",
philpem@72 332 (state.leds & 2) ? "Y" : "-",
philpem@72 333 (state.leds & 1) ? "R" : "-");
philpem@72 334 }
philpem@46 335 handled = true;
philpem@40 336 break;
philpem@40 337 case 0x0B0000: // TM/DIALWR
philpem@40 338 break;
philpem@59 339 case 0x0C0000: // Clear Status Register
philpem@59 340 state.genstat = 0xFFFF;
philpem@59 341 state.bsr0 = 0xFFFF;
philpem@59 342 state.bsr1 = 0xFFFF;
philpem@43 343 handled = true;
philpem@40 344 break;
philpem@40 345 case 0x0D0000: // DMA Address Register
philpem@59 346 if (address & 0x004000) {
philpem@59 347 // A14 high -- set most significant bits
philpem@59 348 state.dma_address = (state.dma_address & 0x1fe) | ((address & 0x3ffe) << 8);
philpem@59 349 } else {
philpem@59 350 // A14 low -- set least significant bits
philpem@59 351 state.dma_address = (state.dma_address & 0x3ffe00) | (address & 0x1fe);
philpem@59 352 }
philpem@59 353 handled = true;
philpem@40 354 break;
philpem@40 355 case 0x0E0000: // Disk Control Register
philpem@66 356 ENFORCE_SIZE_W(bits, address, 16, "DISKCON");
philpem@59 357 // B7 = FDD controller reset
philpem@59 358 if ((data & 0x80) == 0) wd2797_reset(&state.fdc_ctx);
philpem@59 359 // B6 = drive 0 select -- TODO
philpem@59 360 // B5 = motor enable -- TODO
philpem@59 361 // B4 = HDD controller reset -- TODO
philpem@59 362 // B3 = HDD0 select -- TODO
philpem@59 363 // B2,1,0 = HDD0 head select
philpem@59 364 handled = true;
philpem@40 365 break;
philpem@40 366 case 0x0F0000: // Line Printer Data Register
philpem@40 367 break;
philpem@40 368 }
philpem@40 369 } else if ((address >= 0xC00000) && (address <= 0xFFFFFF)) {
philpem@40 370 // I/O register space, zone B
philpem@40 371 switch (address & 0xF00000) {
philpem@40 372 case 0xC00000: // Expansion slots
philpem@40 373 case 0xD00000:
philpem@40 374 switch (address & 0xFC0000) {
philpem@40 375 case 0xC00000: // Expansion slot 0
philpem@40 376 case 0xC40000: // Expansion slot 1
philpem@40 377 case 0xC80000: // Expansion slot 2
philpem@40 378 case 0xCC0000: // Expansion slot 3
philpem@40 379 case 0xD00000: // Expansion slot 4
philpem@40 380 case 0xD40000: // Expansion slot 5
philpem@40 381 case 0xD80000: // Expansion slot 6
philpem@40 382 case 0xDC0000: // Expansion slot 7
philpem@59 383 fprintf(stderr, "NOTE: WR%d to expansion card space, addr=0x%08X, data=0x%08X\n", bits, address, data);
philpem@59 384 handled = true;
philpem@40 385 break;
philpem@40 386 }
philpem@40 387 break;
philpem@40 388 case 0xE00000: // HDC, FDC, MCR2 and RTC data bits
philpem@40 389 case 0xF00000:
philpem@40 390 switch (address & 0x070000) {
philpem@40 391 case 0x000000: // [ef][08]xxxx ==> WD1010 hard disc controller
philpem@40 392 break;
philpem@40 393 case 0x010000: // [ef][19]xxxx ==> WD2797 floppy disc controller
philpem@66 394 ENFORCE_SIZE_W(bits, address, 16, "FDC REGISTERS");
philpem@59 395 wd2797_write_reg(&state.fdc_ctx, (address >> 1) & 3, data);
philpem@52 396 handled = true;
philpem@40 397 break;
philpem@40 398 case 0x020000: // [ef][2a]xxxx ==> Miscellaneous Control Register 2
philpem@40 399 break;
philpem@40 400 case 0x030000: // [ef][3b]xxxx ==> Real Time Clock data bits
philpem@40 401 break;
philpem@40 402 case 0x040000: // [ef][4c]xxxx ==> General Control Register
philpem@40 403 switch (address & 0x077000) {
philpem@40 404 case 0x040000: // [ef][4c][08]xxx ==> EE
philpem@102 405 // Error Enable. If =0, Level7 intrs and bus errors are masked.
philpem@102 406 ENFORCE_SIZE_W(bits, address, 16, "EE");
philpem@102 407 state.ee = ((data & 0x8000) == 0x8000);
philpem@102 408 handled = true;
philpem@59 409 break;
philpem@44 410 case 0x041000: // [ef][4c][19]xxx ==> PIE
philpem@66 411 ENFORCE_SIZE_W(bits, address, 16, "PIE");
philpem@59 412 state.pie = ((data & 0x8000) == 0x8000);
philpem@59 413 handled = true;
philpem@59 414 break;
philpem@40 415 case 0x042000: // [ef][4c][2A]xxx ==> BP
philpem@59 416 break;
philpem@40 417 case 0x043000: // [ef][4c][3B]xxx ==> ROMLMAP
philpem@66 418 ENFORCE_SIZE_W(bits, address, 16, "ROMLMAP");
philpem@59 419 state.romlmap = ((data & 0x8000) == 0x8000);
philpem@44 420 handled = true;
philpem@40 421 break;
philpem@59 422 case 0x044000: // [ef][4c][4C]xxx ==> L1 MODEM
philpem@66 423 ENFORCE_SIZE_W(bits, address, 16, "L1 MODEM");
philpem@59 424 break;
philpem@59 425 case 0x045000: // [ef][4c][5D]xxx ==> L2 MODEM
philpem@66 426 ENFORCE_SIZE_W(bits, address, 16, "L2 MODEM");
philpem@59 427 break;
philpem@59 428 case 0x046000: // [ef][4c][6E]xxx ==> D/N CONNECT
philpem@66 429 ENFORCE_SIZE_W(bits, address, 16, "D/N CONNECT");
philpem@59 430 break;
philpem@59 431 case 0x047000: // [ef][4c][7F]xxx ==> Whole screen reverse video
philpem@66 432 ENFORCE_SIZE_W(bits, address, 16, "WHOLE SCREEN REVERSE VIDEO");
philpem@40 433 break;
philpem@40 434 }
philpem@40 435 case 0x050000: // [ef][5d]xxxx ==> 8274
philpem@40 436 break;
philpem@40 437 case 0x060000: // [ef][6e]xxxx ==> Control regs
philpem@40 438 switch (address & 0x07F000) {
philpem@40 439 default:
philpem@40 440 break;
philpem@40 441 }
philpem@40 442 break;
philpem@40 443 case 0x070000: // [ef][7f]xxxx ==> 6850 Keyboard Controller
philpem@84 444 // TODO: figure out which sizes are valid (probably just 8 and 16)
philpem@84 445 // ENFORCE_SIZE_W(bits, address, 16, "KEYBOARD CONTROLLER");
philpem@93 446 if (bits == 8) {
philpem@93 447 printf("KBD WR %02X => %02X\n", (address >> 1) & 3, data);
philpem@93 448 keyboard_write(&state.kbd, (address >> 1) & 3, data);
philpem@93 449 handled = true;
philpem@93 450 } else if (bits == 16) {
philpem@93 451 printf("KBD WR %02X => %04X\n", (address >> 1) & 3, data);
philpem@93 452 keyboard_write(&state.kbd, (address >> 1) & 3, data >> 8);
philpem@93 453 handled = true;
philpem@93 454 }
philpem@40 455 break;
philpem@40 456 }
philpem@40 457 }
philpem@40 458 }
philpem@40 459
philpem@64 460 LOG_NOT_HANDLED_W(bits);
philpem@59 461 }/*}}}*/
philpem@40 462
philpem@59 463 uint32_t IoRead(uint32_t address, int bits)/*{{{*/
philpem@59 464 {
philpem@59 465 bool handled = false;
philpem@59 466 uint32_t data = 0xFFFFFFFF;
philpem@40 467
philpem@59 468 if ((address >= 0x400000) && (address <= 0x7FFFFF)) {
philpem@40 469 // I/O register space, zone A
philpem@40 470 switch (address & 0x0F0000) {
philpem@40 471 case 0x010000: // General Status Register
philpem@66 472 ENFORCE_SIZE_R(bits, address, 16, "GENSTAT");
philpem@59 473 return ((uint32_t)state.genstat << 16) + (uint32_t)state.genstat;
philpem@40 474 break;
philpem@40 475 case 0x030000: // Bus Status Register 0
philpem@66 476 ENFORCE_SIZE_R(bits, address, 16, "BSR0");
philpem@59 477 return ((uint32_t)state.bsr0 << 16) + (uint32_t)state.bsr0;
philpem@40 478 break;
philpem@40 479 case 0x040000: // Bus Status Register 1
philpem@66 480 ENFORCE_SIZE_R(bits, address, 16, "BSR1");
philpem@59 481 return ((uint32_t)state.bsr1 << 16) + (uint32_t)state.bsr1;
philpem@40 482 break;
philpem@40 483 case 0x050000: // Phone status
philpem@66 484 ENFORCE_SIZE_R(bits, address, 8 | 16, "PHONE STATUS");
philpem@40 485 break;
philpem@40 486 case 0x060000: // DMA Count
philpem@55 487 // TODO: U/OERR- is always inactive (bit set)... or should it be = DMAEN+?
philpem@55 488 // Bit 14 is always unused, so leave it set
philpem@66 489 ENFORCE_SIZE_R(bits, address, 16, "DMACOUNT");
philpem@59 490 return (state.dma_count & 0x3fff) | 0xC000;
philpem@40 491 break;
philpem@40 492 case 0x070000: // Line Printer Status Register
philpem@53 493 data = 0x00120012; // no parity error, no line printer error, no irqs from FDD or HDD
philpem@78 494 data |= wd2797_get_irq(&state.fdc_ctx) ? 0x00080008 : 0;
philpem@59 495 return data;
philpem@40 496 break;
philpem@40 497 case 0x080000: // Real Time Clock
philpem@59 498 printf("READ NOTIMP: Realtime Clock\n");
philpem@40 499 break;
philpem@40 500 case 0x090000: // Phone registers
philpem@40 501 switch (address & 0x0FF000) {
philpem@40 502 case 0x090000: // Handset relay
philpem@40 503 case 0x098000:
philpem@40 504 break;
philpem@40 505 case 0x091000: // Line select 2
philpem@40 506 case 0x099000:
philpem@40 507 break;
philpem@40 508 case 0x092000: // Hook relay 1
philpem@40 509 case 0x09A000:
philpem@40 510 break;
philpem@40 511 case 0x093000: // Hook relay 2
philpem@40 512 case 0x09B000:
philpem@40 513 break;
philpem@40 514 case 0x094000: // Line 1 hold
philpem@40 515 case 0x09C000:
philpem@40 516 break;
philpem@40 517 case 0x095000: // Line 2 hold
philpem@40 518 case 0x09D000:
philpem@40 519 break;
philpem@40 520 case 0x096000: // Line 1 A-lead
philpem@40 521 case 0x09E000:
philpem@40 522 break;
philpem@40 523 case 0x097000: // Line 2 A-lead
philpem@40 524 case 0x09F000:
philpem@40 525 break;
philpem@40 526 }
philpem@40 527 break;
philpem@46 528 case 0x0A0000: // Miscellaneous Control Register -- write only!
philpem@46 529 handled = true;
philpem@40 530 break;
philpem@40 531 case 0x0B0000: // TM/DIALWR
philpem@40 532 break;
philpem@46 533 case 0x0C0000: // Clear Status Register -- write only!
philpem@43 534 handled = true;
philpem@40 535 break;
philpem@40 536 case 0x0D0000: // DMA Address Register
philpem@40 537 break;
philpem@40 538 case 0x0E0000: // Disk Control Register
philpem@40 539 break;
philpem@40 540 case 0x0F0000: // Line Printer Data Register
philpem@40 541 break;
philpem@40 542 }
philpem@40 543 } else if ((address >= 0xC00000) && (address <= 0xFFFFFF)) {
philpem@40 544 // I/O register space, zone B
philpem@40 545 switch (address & 0xF00000) {
philpem@40 546 case 0xC00000: // Expansion slots
philpem@40 547 case 0xD00000:
philpem@40 548 switch (address & 0xFC0000) {
philpem@40 549 case 0xC00000: // Expansion slot 0
philpem@40 550 case 0xC40000: // Expansion slot 1
philpem@40 551 case 0xC80000: // Expansion slot 2
philpem@40 552 case 0xCC0000: // Expansion slot 3
philpem@40 553 case 0xD00000: // Expansion slot 4
philpem@40 554 case 0xD40000: // Expansion slot 5
philpem@40 555 case 0xD80000: // Expansion slot 6
philpem@40 556 case 0xDC0000: // Expansion slot 7
philpem@65 557 fprintf(stderr, "NOTE: RD%d from expansion card space, addr=0x%08X\n", bits, address);
philpem@65 558 handled = true;
philpem@40 559 break;
philpem@40 560 }
philpem@40 561 break;
philpem@40 562 case 0xE00000: // HDC, FDC, MCR2 and RTC data bits
philpem@40 563 case 0xF00000:
philpem@40 564 switch (address & 0x070000) {
philpem@40 565 case 0x000000: // [ef][08]xxxx ==> WD1010 hard disc controller
philpem@40 566 break;
philpem@40 567 case 0x010000: // [ef][19]xxxx ==> WD2797 floppy disc controller
philpem@66 568 ENFORCE_SIZE_R(bits, address, 16, "FDC REGISTERS");
philpem@59 569 return wd2797_read_reg(&state.fdc_ctx, (address >> 1) & 3);
philpem@40 570 break;
philpem@40 571 case 0x020000: // [ef][2a]xxxx ==> Miscellaneous Control Register 2
philpem@40 572 break;
philpem@40 573 case 0x030000: // [ef][3b]xxxx ==> Real Time Clock data bits
philpem@40 574 break;
philpem@40 575 case 0x040000: // [ef][4c]xxxx ==> General Control Register
philpem@40 576 switch (address & 0x077000) {
philpem@40 577 case 0x040000: // [ef][4c][08]xxx ==> EE
philpem@44 578 case 0x041000: // [ef][4c][19]xxx ==> PIE
philpem@40 579 case 0x042000: // [ef][4c][2A]xxx ==> BP
philpem@40 580 case 0x043000: // [ef][4c][3B]xxx ==> ROMLMAP
philpem@40 581 case 0x044000: // [ef][4c][4C]xxx ==> L1 MODEM
philpem@40 582 case 0x045000: // [ef][4c][5D]xxx ==> L2 MODEM
philpem@40 583 case 0x046000: // [ef][4c][6E]xxx ==> D/N CONNECT
philpem@44 584 // All write-only registers... TODO: bus error?
philpem@44 585 handled = true;
philpem@40 586 break;
philpem@44 587 case 0x047000: // [ef][4c][7F]xxx ==> Whole screen reverse video [FIXME: not in TRM]
philpem@40 588 break;
philpem@40 589 }
philpem@40 590 break;
philpem@40 591 case 0x050000: // [ef][5d]xxxx ==> 8274
philpem@40 592 break;
philpem@40 593 case 0x060000: // [ef][6e]xxxx ==> Control regs
philpem@40 594 switch (address & 0x07F000) {
philpem@40 595 default:
philpem@40 596 break;
philpem@40 597 }
philpem@40 598 break;
philpem@40 599 case 0x070000: // [ef][7f]xxxx ==> 6850 Keyboard Controller
philpem@84 600 // TODO: figure out which sizes are valid (probably just 8 and 16)
philpem@84 601 //ENFORCE_SIZE_R(bits, address, 16, "KEYBOARD CONTROLLER");
philpem@84 602 {
philpem@93 603 if (bits == 8) {
philpem@93 604 return keyboard_read(&state.kbd, (address >> 1) & 3);
philpem@93 605 } else {
philpem@93 606 return keyboard_read(&state.kbd, (address >> 1) & 3) << 8;
philpem@93 607 }
philpem@84 608 return data;
philpem@84 609 }
philpem@40 610 break;
philpem@40 611 }
philpem@40 612 }
philpem@40 613 }
philpem@40 614
philpem@64 615 LOG_NOT_HANDLED_R(bits);
philpem@64 616
philpem@59 617 return data;
philpem@59 618 }/*}}}*/
philpem@40 619
philpem@59 620
philpem@59 621 /********************************************************
philpem@59 622 * m68k memory read/write support functions for Musashi
philpem@59 623 ********************************************************/
philpem@59 624
philpem@59 625 /**
philpem@59 626 * @brief Read M68K memory, 32-bit
philpem@59 627 */
philpem@59 628 uint32_t m68k_read_memory_32(uint32_t address)/*{{{*/
philpem@59 629 {
philpem@59 630 uint32_t data = 0xFFFFFFFF;
philpem@59 631
philpem@59 632 // If ROMLMAP is set, force system to access ROM
philpem@59 633 if (!state.romlmap)
philpem@59 634 address |= 0x800000;
philpem@59 635
philpem@59 636 // Check access permissions
philpem@59 637 ACCESS_CHECK_RD(address, 32);
philpem@59 638
philpem@59 639 if ((address >= 0x800000) && (address <= 0xBFFFFF)) {
philpem@59 640 // ROM access
philpem@60 641 return RD32(state.rom, address, ROM_SIZE - 1);
philpem@60 642 } else if (address <= 0x3fffff) {
philpem@59 643 // RAM access
philpem@60 644 uint32_t newAddr = mapAddr(address, false);
philpem@63 645 if (newAddr <= 0x1fffff) {
philpem@60 646 return RD32(state.base_ram, newAddr, state.base_ram_size - 1);
philpem@63 647 } else {
philpem@63 648 if (newAddr <= (state.exp_ram_size + 0x200000 - 1))
philpem@63 649 return RD32(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1);
philpem@63 650 else
philpem@63 651 return 0xffffffff;
philpem@63 652 }
philpem@59 653 } else if ((address >= 0x400000) && (address <= 0x7FFFFF)) {
philpem@59 654 // I/O register space, zone A
philpem@59 655 switch (address & 0x0F0000) {
philpem@59 656 case 0x000000: // Map RAM access
philpem@59 657 if (address > 0x4007FF) fprintf(stderr, "NOTE: RD32 from MapRAM mirror, addr=0x%08X\n", address);
philpem@60 658 return RD32(state.map, address, 0x7FF);
philpem@59 659 break;
philpem@59 660 case 0x020000: // Video RAM
philpem@59 661 if (address > 0x427FFF) fprintf(stderr, "NOTE: RD32 from VideoRAM mirror, addr=0x%08X\n", address);
philpem@60 662 return RD32(state.vram, address, 0x7FFF);
philpem@59 663 break;
philpem@59 664 default:
philpem@60 665 return IoRead(address, 32);
philpem@59 666 }
philpem@59 667 } else {
philpem@60 668 return IoRead(address, 32);
philpem@59 669 }
philpem@59 670
philpem@40 671 return data;
philpem@59 672 }/*}}}*/
philpem@40 673
philpem@40 674 /**
philpem@40 675 * @brief Read M68K memory, 16-bit
philpem@40 676 */
philpem@59 677 uint32_t m68k_read_memory_16(uint32_t address)/*{{{*/
philpem@40 678 {
philpem@40 679 uint16_t data = 0xFFFF;
philpem@40 680
philpem@40 681 // If ROMLMAP is set, force system to access ROM
philpem@40 682 if (!state.romlmap)
philpem@40 683 address |= 0x800000;
philpem@40 684
philpem@40 685 // Check access permissions
philpem@40 686 ACCESS_CHECK_RD(address, 16);
philpem@40 687
philpem@40 688 if ((address >= 0x800000) && (address <= 0xBFFFFF)) {
philpem@40 689 // ROM access
philpem@40 690 data = RD16(state.rom, address, ROM_SIZE - 1);
philpem@60 691 } else if (address <= 0x3fffff) {
philpem@40 692 // RAM access
philpem@60 693 uint32_t newAddr = mapAddr(address, false);
philpem@63 694 if (newAddr <= 0x1fffff) {
philpem@60 695 return RD16(state.base_ram, newAddr, state.base_ram_size - 1);
philpem@63 696 } else {
philpem@63 697 if (newAddr <= (state.exp_ram_size + 0x200000 - 1))
philpem@63 698 return RD16(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1);
philpem@63 699 else
philpem@63 700 return 0xffff;
philpem@63 701 }
philpem@40 702 } else if ((address >= 0x400000) && (address <= 0x7FFFFF)) {
philpem@40 703 // I/O register space, zone A
philpem@40 704 switch (address & 0x0F0000) {
philpem@40 705 case 0x000000: // Map RAM access
philpem@40 706 if (address > 0x4007FF) fprintf(stderr, "NOTE: RD16 from MapRAM mirror, addr=0x%08X\n", address);
philpem@40 707 data = RD16(state.map, address, 0x7FF);
philpem@40 708 break;
philpem@40 709 case 0x020000: // Video RAM
philpem@40 710 if (address > 0x427FFF) fprintf(stderr, "NOTE: RD16 from VideoRAM mirror, addr=0x%08X\n", address);
philpem@40 711 data = RD16(state.vram, address, 0x7FFF);
philpem@40 712 break;
philpem@59 713 default:
philpem@59 714 data = IoRead(address, 16);
philpem@40 715 }
philpem@59 716 } else {
philpem@59 717 data = IoRead(address, 16);
philpem@40 718 }
philpem@40 719
philpem@40 720 return data;
philpem@59 721 }/*}}}*/
philpem@40 722
philpem@40 723 /**
philpem@40 724 * @brief Read M68K memory, 8-bit
philpem@40 725 */
philpem@59 726 uint32_t m68k_read_memory_8(uint32_t address)/*{{{*/
philpem@40 727 {
philpem@40 728 uint8_t data = 0xFF;
philpem@40 729
philpem@40 730 // If ROMLMAP is set, force system to access ROM
philpem@40 731 if (!state.romlmap)
philpem@40 732 address |= 0x800000;
philpem@40 733
philpem@40 734 // Check access permissions
philpem@40 735 ACCESS_CHECK_RD(address, 8);
philpem@40 736
philpem@40 737 if ((address >= 0x800000) && (address <= 0xBFFFFF)) {
philpem@40 738 // ROM access
philpem@40 739 data = RD8(state.rom, address, ROM_SIZE - 1);
philpem@60 740 } else if (address <= 0x3fffff) {
philpem@40 741 // RAM access
philpem@60 742 uint32_t newAddr = mapAddr(address, false);
philpem@63 743 if (newAddr <= 0x1fffff) {
philpem@60 744 return RD8(state.base_ram, newAddr, state.base_ram_size - 1);
philpem@63 745 } else {
philpem@63 746 if (newAddr <= (state.exp_ram_size + 0x200000 - 1))
philpem@63 747 return RD8(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1);
philpem@63 748 else
philpem@63 749 return 0xff;
philpem@63 750 }
philpem@40 751 } else if ((address >= 0x400000) && (address <= 0x7FFFFF)) {
philpem@40 752 // I/O register space, zone A
philpem@40 753 switch (address & 0x0F0000) {
philpem@40 754 case 0x000000: // Map RAM access
philpem@40 755 if (address > 0x4007FF) fprintf(stderr, "NOTE: RD8 from MapRAM mirror, addr=0x%08X\n", address);
philpem@40 756 data = RD8(state.map, address, 0x7FF);
philpem@40 757 break;
philpem@40 758 case 0x020000: // Video RAM
philpem@40 759 if (address > 0x427FFF) fprintf(stderr, "NOTE: RD8 from VideoRAM mirror, addr=0x%08X\n", address);
philpem@40 760 data = RD8(state.vram, address, 0x7FFF);
philpem@40 761 break;
philpem@59 762 default:
philpem@59 763 data = IoRead(address, 8);
philpem@40 764 }
philpem@59 765 } else {
philpem@59 766 data = IoRead(address, 8);
philpem@40 767 }
philpem@40 768
philpem@40 769 return data;
philpem@59 770 }/*}}}*/
philpem@40 771
philpem@40 772 /**
philpem@40 773 * @brief Write M68K memory, 32-bit
philpem@40 774 */
philpem@59 775 void m68k_write_memory_32(uint32_t address, uint32_t value)/*{{{*/
philpem@40 776 {
philpem@40 777 // If ROMLMAP is set, force system to access ROM
philpem@40 778 if (!state.romlmap)
philpem@40 779 address |= 0x800000;
philpem@40 780
philpem@40 781 // Check access permissions
philpem@40 782 ACCESS_CHECK_WR(address, 32);
philpem@40 783
philpem@40 784 if ((address >= 0x800000) && (address <= 0xBFFFFF)) {
philpem@40 785 // ROM access
philpem@60 786 } else if (address <= 0x3FFFFF) {
philpem@40 787 // RAM access
philpem@60 788 uint32_t newAddr = mapAddr(address, true);
philpem@70 789 if (newAddr <= 0x1fffff)
philpem@60 790 WR32(state.base_ram, newAddr, state.base_ram_size - 1, value);
philpem@70 791 else
philpem@65 792 WR32(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1, value);
philpem@40 793 } else if ((address >= 0x400000) && (address <= 0x7FFFFF)) {
philpem@40 794 // I/O register space, zone A
philpem@40 795 switch (address & 0x0F0000) {
philpem@40 796 case 0x000000: // Map RAM access
philpem@105 797 if (address > 0x4007FF) fprintf(stderr, "NOTE: WR32 to MapRAM mirror, addr=0x%08X\n", address);
philpem@40 798 WR32(state.map, address, 0x7FF, value);
philpem@40 799 break;
philpem@40 800 case 0x020000: // Video RAM
philpem@105 801 if (address > 0x427FFF) fprintf(stderr, "NOTE: WR32 to VideoRAM mirror, addr=0x%08X\n", address);
philpem@40 802 WR32(state.vram, address, 0x7FFF, value);
philpem@40 803 break;
philpem@59 804 default:
philpem@59 805 IoWrite(address, value, 32);
philpem@40 806 }
philpem@59 807 } else {
philpem@59 808 IoWrite(address, value, 32);
philpem@40 809 }
philpem@59 810 }/*}}}*/
philpem@40 811
philpem@40 812 /**
philpem@40 813 * @brief Write M68K memory, 16-bit
philpem@40 814 */
philpem@59 815 void m68k_write_memory_16(uint32_t address, uint32_t value)/*{{{*/
philpem@40 816 {
philpem@40 817 // If ROMLMAP is set, force system to access ROM
philpem@40 818 if (!state.romlmap)
philpem@40 819 address |= 0x800000;
philpem@40 820
philpem@40 821 // Check access permissions
philpem@40 822 ACCESS_CHECK_WR(address, 16);
philpem@40 823
philpem@40 824 if ((address >= 0x800000) && (address <= 0xBFFFFF)) {
philpem@40 825 // ROM access
philpem@60 826 } else if (address <= 0x3FFFFF) {
philpem@40 827 // RAM access
philpem@60 828 uint32_t newAddr = mapAddr(address, true);
philpem@70 829 if (newAddr <= 0x1fffff)
philpem@60 830 WR16(state.base_ram, newAddr, state.base_ram_size - 1, value);
philpem@70 831 else
philpem@65 832 WR16(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1, value);
philpem@40 833 } else if ((address >= 0x400000) && (address <= 0x7FFFFF)) {
philpem@40 834 // I/O register space, zone A
philpem@40 835 switch (address & 0x0F0000) {
philpem@40 836 case 0x000000: // Map RAM access
philpem@40 837 if (address > 0x4007FF) fprintf(stderr, "NOTE: WR16 to MapRAM mirror, addr=0x%08X, data=0x%04X\n", address, value);
philpem@40 838 WR16(state.map, address, 0x7FF, value);
philpem@40 839 break;
philpem@40 840 case 0x020000: // Video RAM
philpem@40 841 if (address > 0x427FFF) fprintf(stderr, "NOTE: WR16 to VideoRAM mirror, addr=0x%08X, data=0x%04X\n", address, value);
philpem@40 842 WR16(state.vram, address, 0x7FFF, value);
philpem@40 843 break;
philpem@59 844 default:
philpem@59 845 IoWrite(address, value, 16);
philpem@40 846 }
philpem@59 847 } else {
philpem@59 848 IoWrite(address, value, 16);
philpem@40 849 }
philpem@59 850 }/*}}}*/
philpem@40 851
philpem@40 852 /**
philpem@40 853 * @brief Write M68K memory, 8-bit
philpem@40 854 */
philpem@59 855 void m68k_write_memory_8(uint32_t address, uint32_t value)/*{{{*/
philpem@40 856 {
philpem@40 857 // If ROMLMAP is set, force system to access ROM
philpem@40 858 if (!state.romlmap)
philpem@40 859 address |= 0x800000;
philpem@40 860
philpem@40 861 // Check access permissions
philpem@40 862 ACCESS_CHECK_WR(address, 8);
philpem@40 863
philpem@40 864 if ((address >= 0x800000) && (address <= 0xBFFFFF)) {
philpem@40 865 // ROM access (read only!)
philpem@60 866 } else if (address <= 0x3FFFFF) {
philpem@40 867 // RAM access
philpem@60 868 uint32_t newAddr = mapAddr(address, true);
philpem@70 869 if (newAddr <= 0x1fffff)
philpem@60 870 WR8(state.base_ram, newAddr, state.base_ram_size - 1, value);
philpem@70 871 else
philpem@65 872 WR8(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1, value);
philpem@40 873 } else if ((address >= 0x400000) && (address <= 0x7FFFFF)) {
philpem@40 874 // I/O register space, zone A
philpem@40 875 switch (address & 0x0F0000) {
philpem@40 876 case 0x000000: // Map RAM access
philpem@59 877 if (address > 0x4007FF) fprintf(stderr, "NOTE: WR8 to MapRAM mirror, addr=0x%08X, data=0x%04X\n", address, value);
philpem@40 878 WR8(state.map, address, 0x7FF, value);
philpem@40 879 break;
philpem@40 880 case 0x020000: // Video RAM
philpem@59 881 if (address > 0x427FFF) fprintf(stderr, "NOTE: WR8 to VideoRAM mirror, addr=0x%08X, data=0x%04X\n", address, value);
philpem@40 882 WR8(state.vram, address, 0x7FFF, value);
philpem@40 883 break;
philpem@59 884 default:
philpem@59 885 IoWrite(address, value, 8);
philpem@40 886 }
philpem@59 887 } else {
philpem@59 888 IoWrite(address, value, 8);
philpem@40 889 }
philpem@59 890 }/*}}}*/
philpem@40 891
philpem@40 892
philpem@40 893 // for the disassembler
philpem@40 894 uint32_t m68k_read_disassembler_32(uint32_t addr) { return m68k_read_memory_32(addr); }
philpem@40 895 uint32_t m68k_read_disassembler_16(uint32_t addr) { return m68k_read_memory_16(addr); }
philpem@40 896 uint32_t m68k_read_disassembler_8 (uint32_t addr) { return m68k_read_memory_8 (addr); }
philpem@40 897