/* Emulation of MC68030 MMU * This code has been written for Previous - a NeXT Computer emulator * * This file is distributed under the GNU General Public License, version 2 * or at your option any later version. Read the file gpl.txt for details. * * * Written by Andreas Grabher * * Many thanks go to Thomas Huth and the Hatari community for helping * to test and debug this code! * * * Release notes: * 01-09-2012: First release * 29-09-2012: Improved function code handling * 16-11-2012: Improved exception handling * * * - Check if read-modify-write operations are correctly detected for * handling transparent access (see TT matching functions) * - If possible, test mmu030_table_search with all kinds of translations * (early termination, invalid descriptors, bus errors, indirect * descriptors, PTEST in different levels, etc). * - Check which bits of an ATC entry or the status register should be set * and which should be un-set, if an invalid translation occurs. * - Handle cache inhibit bit when accessing ATC entries */ #include "sysconfig.h" #include "sysdeps.h" #include "options.h" #include "memory.h" #include "newcpu.h" #include "debug.h" #include "cpummu030.h" #define MMU030_OP_DBG_MSG 0 #define MMU030_ATC_DBG_MSG 0 #define MMU030_REG_DBG_MSG 0 #define TT_FC_MASK 0x00000007 #define TT_FC_BASE 0x00000070 #define TT_RWM 0x00000100 #define TT_RW 0x00000200 #define TT_CI 0x00000400 #define TT_ENABLE 0x00008000 #define TT_ADDR_MASK 0x00FF0000 #define TT_ADDR_BASE 0xFF000000 static int bBusErrorReadWrite; static int atcindextable[32]; static int tt_enabled; int mmu030_idx; uae_u32 mm030_stageb_address; bool mmu030_retry; int mmu030_opcode; int mmu030_opcode_stageb; int mmu030_fake_prefetch; uaecptr mmu030_fake_prefetch_addr; uae_u16 mmu030_state[3]; uae_u32 mmu030_data_buffer; uae_u32 mmu030_disp_store[2]; uae_u32 mmu030_fmovem_store[2]; uae_u8 mmu030_cache_state; struct mmu030_access mmu030_ad[MAX_MMU030_ACCESS]; #if MMU_DPAGECACHE030 #define MMUFASTCACHE_ENTRIES030 256 struct mmufastcache030 { uae_u32 log; uae_u32 phys; }; static struct mmufastcache030 atc_data_cache_read[MMUFASTCACHE_ENTRIES030]; static struct mmufastcache030 atc_data_cache_write[MMUFASTCACHE_ENTRIES030]; #endif /* for debugging messages */ char table_letter[4] = {'A','B','C','D'}; static const uae_u32 mmu030_size[3] = { MMU030_SSW_SIZE_B, MMU030_SSW_SIZE_W, MMU030_SSW_SIZE_L }; uae_u64 srp_030, crp_030; uae_u32 tt0_030, tt1_030, tc_030; uae_u16 mmusr_030; /* ATC struct */ #define ATC030_NUM_ENTRIES 22 typedef struct { struct { uaecptr addr; bool modified; bool write_protect; uae_u8 cache_inhibit; bool bus_error; } physical; struct { uaecptr addr; uae_u32 fc; bool valid; } logical; /* history bit */ int mru; } MMU030_ATC_LINE; /* MMU struct for 68030 */ static struct { /* Translation tables */ struct { struct { uae_u32 mask; uae_u8 shift; } table[4]; struct { uae_u32 mask; uae_u32 imask; uae_u32 size; uae_u32 size3m; } page; uae_u8 init_shift; uae_u8 last_table; } translation; /* Transparent translation */ struct { TT_info tt0; TT_info tt1; } transparent; /* Address translation cache */ MMU030_ATC_LINE atc[ATC030_NUM_ENTRIES]; /* Condition */ bool enabled; uae_u16 status; #if MMU_IPAGECACHE030 uae_u8 mmu030_cache_state; #if MMU_DIRECT_ACCESS uae_u8 *mmu030_last_physical_address_real; #else uae_u32 mmu030_last_physical_address; #endif uae_u32 mmu030_last_logical_address; #endif } mmu030; /* MMU Status Register * * ---x ---x x-xx x--- * reserved (all 0) * * x--- ---- ---- ---- * bus error * * -x-- ---- ---- ---- * limit violation * * --x- ---- ---- ---- * supervisor only * * ---- x--- ---- ---- * write protected * * ---- -x-- ---- ---- * invalid * * ---- --x- ---- ---- * modified * * ---- ---- -x-- ---- * transparent access * * ---- ---- ---- -xxx * number of levels (number of tables accessed during search) * */ #define MMUSR_BUS_ERROR 0x8000 #define MMUSR_LIMIT_VIOLATION 0x4000 #define MMUSR_SUPER_VIOLATION 0x2000 #define MMUSR_WRITE_PROTECTED 0x0800 #define MMUSR_INVALID 0x0400 #define MMUSR_MODIFIED 0x0200 #define MMUSR_TRANSP_ACCESS 0x0040 #define MMUSR_NUM_LEVELS_MASK 0x0007 /* -- ATC flushing functions -- */ static void mmu030_flush_cache(uaecptr addr) { #if MMU_IPAGECACHE030 mmu030.mmu030_last_logical_address = 0xffffffff; #endif #if MMU_DPAGECACHE030 if (addr == 0xffffffff) { memset(&atc_data_cache_read, 0xff, sizeof atc_data_cache_read); memset(&atc_data_cache_write, 0xff, sizeof atc_data_cache_write); } else { uae_u32 idx = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | 7; for (int i = 0; i < MMUFASTCACHE_ENTRIES030; i++) { if ((atc_data_cache_read[i].log | 7) == idx) atc_data_cache_read[i].log = 0xffffffff; if ((atc_data_cache_write[i].log | 7) == idx) atc_data_cache_write[i].log = 0xffffffff; } } #endif } /* This function flushes ATC entries depending on their function code */ static void mmu030_flush_atc_fc(uae_u32 fc_base, uae_u32 fc_mask) { int i; for (i=0; i> 3) & 7; int rreg = opcode & 7; // Dn, An, (An)+, -(An), immediate and PC-relative not allowed if (eamode == 0 || eamode == 1 || eamode == 3 || eamode == 4 || eamode == 6 || (eamode == 7 && rreg > 1)) return true; return false; } bool mmu_op30_pmove (uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra) { int preg = (next >> 10) & 31; int rw = (next >> 9) & 1; int fd = (next >> 8) & 1; int unused = (next & 0xff); if (mmu_op30_invea(opcode)) return true; // unused low 8 bits must be zeroed if (unused) return true; // read and fd set? if (rw && fd) return true; #if MMU030_OP_DBG_MSG switch (preg) { case 0x10: write_log(_T("PMOVE: %s TC %08X PC=%08x\n"), rw ? _T("read"): _T("write"), rw ? tc_030 : x_get_long(extra), m68k_getpc()); break; case 0x12: write_log(_T("PMOVE: %s SRP %08X%08X PC=%08x\n"), rw ? _T("read") : _T("write"), rw?(uae_u32)(srp_030>>32)&0xFFFFFFFF:x_get_long(extra), rw?(uae_u32)srp_030&0xFFFFFFFF:x_get_long(extra+4), m68k_getpc()); break; case 0x13: write_log(_T("PMOVE: %s CRP %08X%08X PC=%08x\n"), rw ? _T("read") : _T("write"), rw?(uae_u32)(crp_030>>32)&0xFFFFFFFF:x_get_long(extra), rw?(uae_u32)crp_030&0xFFFFFFFF:x_get_long(extra+4), m68k_getpc()); break; case 0x18: write_log(_T("PMOVE: %s MMUSR %04X PC=%08x\n"), rw ? _T("read") : _T("write"), rw?mmusr_030:x_get_word(extra), m68k_getpc()); break; case 0x02: write_log(_T("PMOVE: %s TT0 %08X PC=%08x\n"), rw ? _T("read") : _T("write"), rw?tt0_030:x_get_long(extra), m68k_getpc()); break; case 0x03: write_log(_T("PMOVE: %s TT1 %08X PC=%08x\n"), rw ? _T("read") : _T("write"), rw?tt1_030:x_get_long(extra), m68k_getpc()); break; default: break; } if (!fd && !rw && !(preg==0x18)) { write_log(_T("PMOVE: flush ATC\n")); } #endif switch (preg) { case 0x10: // TC if (rw) x_put_long (extra, tc_030); else { tc_030 = x_get_long (extra); if (mmu030_decode_tc(tc_030, true)) return true; } break; case 0x12: // SRP if (rw) { x_put_long (extra, srp_030 >> 32); x_put_long (extra + 4, srp_030); } else { srp_030 = (uae_u64)x_get_long (extra) << 32; srp_030 |= x_get_long (extra + 4); if (mmu030_decode_rp(srp_030)) return true; } break; case 0x13: // CRP if (rw) { x_put_long (extra, crp_030 >> 32); x_put_long (extra + 4, crp_030); } else { crp_030 = (uae_u64)x_get_long (extra) << 32; crp_030 |= x_get_long (extra + 4); if (mmu030_decode_rp(crp_030)) return true; } break; case 0x18: // MMUSR if (fd) { // FD must be always zero when MMUSR read or write return true; } if (rw) x_put_word (extra, mmusr_030); else mmusr_030 = x_get_word (extra); break; case 0x02: // TT0 if (rw) x_put_long (extra, tt0_030); else { tt0_030 = x_get_long (extra); mmu030.transparent.tt0 = mmu030_decode_tt(tt0_030); } break; case 0x03: // TT1 if (rw) x_put_long (extra, tt1_030); else { tt1_030 = x_get_long (extra); mmu030.transparent.tt1 = mmu030_decode_tt(tt1_030); } break; default: write_log (_T("Bad PMOVE at %08x\n"),m68k_getpc()); return true; } if (!fd && !rw && preg != 0x18) { mmu030_flush_atc_all(); } tt_enabled = (tt0_030 & TT_ENABLE) || (tt1_030 & TT_ENABLE); return false; } bool mmu_op30_ptest (uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra) { mmu030.status = mmusr_030 = 0; int level = (next&0x1C00)>>10; int rw = (next >> 9) & 1; int a = (next >> 8) & 1; int areg = (next&0xE0)>>5; uae_u32 fc = mmu_op30_helper_get_fc(next); bool write = rw ? false : true; uae_u32 ret = 0; if (mmu_op30_invea(opcode)) return true; if (!level && a) { write_log(_T("PTEST: Bad instruction causing F-line unimplemented instruction exception!\n")); return true; } #if MMU030_OP_DBG_MSG write_log(_T("PTEST%c: addr = %08X, fc = %i, level = %i, PC=%08x, "), rw?'R':'W', extra, fc, level, m68k_getpc()); if (a) { write_log(_T("return descriptor to register A%i\n"), areg); } else { write_log(_T("do not return descriptor\n")); } #endif if (!level) { mmu030_ptest_atc_search(extra, fc, write); } else { ret = mmu030_ptest_table_search(extra, fc, write, level); if (a) { m68k_areg (regs, areg) = ret; } } mmusr_030 = mmu030.status; #if MMU030_OP_DBG_MSG write_log(_T("PTEST status: %04X, B = %i, L = %i, S = %i, W = %i, I = %i, M = %i, T = %i, N = %i\n"), mmusr_030, (mmusr_030&MMUSR_BUS_ERROR)?1:0, (mmusr_030&MMUSR_LIMIT_VIOLATION)?1:0, (mmusr_030&MMUSR_SUPER_VIOLATION)?1:0, (mmusr_030&MMUSR_WRITE_PROTECTED)?1:0, (mmusr_030&MMUSR_INVALID)?1:0, (mmusr_030&MMUSR_MODIFIED)?1:0, (mmusr_030&MMUSR_TRANSP_ACCESS)?1:0, mmusr_030&MMUSR_NUM_LEVELS_MASK); #endif return false; } static bool mmu_op30_pload (uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra) { int rw = (next >> 9) & 1; int unused = (next & (0x100 | 0x80 | 0x40 | 0x20)); uae_u32 fc = mmu_op30_helper_get_fc(next); bool write = rw ? false : true; if (mmu_op30_invea(opcode)) return true; if (unused) return true; #if 0 write_log (_T("PLOAD%c: Create ATC entry for %08X, FC = %i\n"), write?'W':'R', extra, fc); #endif mmu030_flush_atc_page(extra); mmu030_table_search(extra, fc, write, 0); return false; } bool mmu_op30_pflush (uaecptr pc, uae_u32 opcode, uae_u16 next, uaecptr extra) { uae_u16 mode = (next >> 8) & 31; uae_u32 fc_mask = (uae_u32)(next & 0x00E0) >> 5; uae_u32 fc_base = mmu_op30_helper_get_fc(next); uae_u32 fc_bits = next & 0x7f; #if 0 switch (mode) { case 0x1: write_log(_T("PFLUSH: Flush all entries\n")); break; case 0x4: write_log(_T("PFLUSH: Flush by function code only\n")); write_log(_T("PFLUSH: function code: base = %08X, mask = %08X\n"), fc_base, fc_mask); break; case 0x6: write_log(_T("PFLUSH: Flush by function code and effective address\n")); write_log(_T("PFLUSH: function code: base = %08X, mask = %08X\n"), fc_base, fc_mask); write_log(_T("PFLUSH: effective address = %08X\n"), extra); break; default: break; } #endif switch (mode) { case 0x00: return mmu_op30_pload(pc, opcode, next, extra); case 0x04: if (fc_bits) return true; mmu030_flush_atc_all(); break; case 0x10: mmu030_flush_atc_fc(fc_base, fc_mask); break; case 0x18: if (mmu_op30_invea(opcode)) return true; mmu030_flush_atc_page_fc(extra, fc_base, fc_mask); break; default: write_log(_T("PFLUSH ERROR: bad mode! (%i)\n"),mode); return true; } return false; } /* -- Helper function for MMU instructions -- */ uae_u32 mmu_op30_helper_get_fc(uae_u16 next) { switch (next&0x0018) { case 0x0010: return (next&0x7); case 0x0008: return (m68k_dreg(regs, next&0x7)&0x7); case 0x0000: if (next&1) { return (regs.dfc); } else { return (regs.sfc); } default: write_log(_T("MMU_OP30 ERROR: bad fc source! (%04X)\n"),next&0x0018); return 0; } } /* Transparent Translation Registers (TT0 and TT1) * * ---- ---- ---- ---- -xxx x--- x--- x--- * reserved, must be 0 * * ---- ---- ---- ---- ---- ---- ---- -xxx * function code mask (FC bits to be ignored) * * ---- ---- ---- ---- ---- ---- -xxx ---- * function code base (FC value for transparent block) * * ---- ---- ---- ---- ---- ---x ---- ---- * 0 = r/w field used, 1 = read and write is transparently translated * * ---- ---- ---- ---- ---- --x- ---- ---- * r/w field: 0 = write ..., 1 = read access transparent * * ---- ---- ---- ---- ---- -x-- ---- ---- * cache inhibit: 0 = caching allowed, 1 = caching inhibited * * ---- ---- ---- ---- x--- ---- ---- ---- * 0 = transparent translation enabled disabled, 1 = enabled * * ---- ---- xxxx xxxx ---- ---- ---- ---- * logical address mask * * xxxx xxxx ---- ---- ---- ---- ---- ---- * logical address base * */ /* TT comparison results */ #define TT_NO_MATCH 0x1 #define TT_OK_MATCH 0x2 #define TT_NO_READ 0x4 #define TT_NO_WRITE 0x8 TT_info mmu030_decode_tt(uae_u32 TT) { TT_info ret; ret.fc_mask = ~((TT&TT_FC_MASK)|0xFFFFFFF8); ret.fc_base = (TT&TT_FC_BASE)>>4; ret.addr_base = TT & TT_ADDR_BASE; ret.addr_mask = ~(((TT&TT_ADDR_MASK)<<8)|0x00FFFFFF); #if 0 if ((TT&TT_ENABLE) && !(TT&TT_RWM)) { write_log(_T("MMU Warning: Transparent translation of read-modify-write cycle is not correctly handled!\n")); } #endif #if MMU030_REG_DBG_MSG /* enable or disable debugging messages */ write_log(_T("\n")); write_log(_T("TRANSPARENT TRANSLATION: %08X\n"), TT); write_log(_T("\n")); write_log(_T("TT: transparent translation ")); if (TT&TT_ENABLE) { write_log(_T("enabled\n")); } else { write_log(_T("disabled\n")); return ret; } write_log(_T("TT: caching %s\n"), (TT&TT_CI) ? _T("inhibited") : _T("enabled")); write_log(_T("TT: read-modify-write ")); if (TT&TT_RWM) { write_log(_T("enabled\n")); } else { write_log(_T("disabled (%s only)\n"), (TT&TT_RW) ? _T("read") : _T("write")); } write_log(_T("\n")); write_log(_T("TT: function code base: %08X\n"), ret.fc_base); write_log(_T("TT: function code mask: %08X\n"), ret.fc_mask); write_log(_T("\n")); write_log(_T("TT: address base: %08X\n"), ret.addr_base); write_log(_T("TT: address mask: %08X\n"), ret.addr_mask); write_log(_T("\n")); #endif return ret; } /* This function checks if an address matches a transparent * translation register */ /* FIXME: * If !(tt&TT_RMW) neither the read nor the write portion * of a read-modify-write cycle is transparently translated! */ static int mmu030_do_match_ttr(uae_u32 tt, TT_info comp, uaecptr addr, uae_u32 fc, bool write) { if (tt & TT_ENABLE) { /* transparent translation enabled */ /* Compare actual function code with function code base using mask */ if ((comp.fc_base&comp.fc_mask)==(fc&comp.fc_mask)) { /* Compare actual address with address base using mask */ if ((comp.addr_base&comp.addr_mask)==(addr&comp.addr_mask)) { if (tt&TT_RWM) { /* r/w field disabled */ return TT_OK_MATCH; } else { if (tt&TT_RW) { /* read access transparent */ return write ? TT_NO_WRITE : TT_OK_MATCH; } else { /* write access transparent */ return write ? TT_OK_MATCH : TT_NO_READ; /* TODO: check this! */ } } } } } return TT_NO_MATCH; } static int mmu030_do_match_lrmw_ttr(uae_u32 tt, TT_info comp, uaecptr addr, uae_u32 fc) { if ((tt & TT_ENABLE) && (tt & TT_RWM)) { /* transparent translation enabled */ /* Compare actual function code with function code base using mask */ if ((comp.fc_base&comp.fc_mask)==(fc&comp.fc_mask)) { /* Compare actual address with address base using mask */ if ((comp.addr_base&comp.addr_mask)==(addr&comp.addr_mask)) { return TT_OK_MATCH; } } } return TT_NO_MATCH; } /* This function compares the address with both transparent * translation registers and returns the result */ static int mmu030_match_ttr(uaecptr addr, uae_u32 fc, bool write) { int tt0, tt1; tt0 = mmu030_do_match_ttr(tt0_030, mmu030.transparent.tt0, addr, fc, write); if (tt0&TT_OK_MATCH) { if (tt0_030&TT_CI) mmu030_cache_state = CACHE_DISABLE_MMU; } tt1 = mmu030_do_match_ttr(tt1_030, mmu030.transparent.tt1, addr, fc, write); if (tt1&TT_OK_MATCH) { if (tt0_030&TT_CI) mmu030_cache_state = CACHE_DISABLE_MMU; } return (tt0|tt1); } static int mmu030_match_ttr_access(uaecptr addr, uae_u32 fc, bool write) { int tt0, tt1; if (!tt_enabled) return 0; tt0 = mmu030_do_match_ttr(tt0_030, mmu030.transparent.tt0, addr, fc, write); tt1 = mmu030_do_match_ttr(tt1_030, mmu030.transparent.tt1, addr, fc, write); return (tt0|tt1) & TT_OK_MATCH; } /* Locked Read-Modify-Write */ static int mmu030_match_lrmw_ttr_access(uaecptr addr, uae_u32 fc) { int tt0, tt1; if (!tt_enabled) return 0; tt0 = mmu030_do_match_lrmw_ttr(tt0_030, mmu030.transparent.tt0, addr, fc); tt1 = mmu030_do_match_lrmw_ttr(tt1_030, mmu030.transparent.tt1, addr, fc); return (tt0|tt1) & TT_OK_MATCH; } /* Translation Control Register: * * x--- ---- ---- ---- ---- ---- ---- ---- * translation: 1 = enable, 0 = disable * * ---- --x- ---- ---- ---- ---- ---- ---- * supervisor root: 1 = enable, 0 = disable * * ---- ---x ---- ---- ---- ---- ---- ---- * function code lookup: 1 = enable, 0 = disable * * ---- ---- xxxx ---- ---- ---- ---- ---- * page size: * 1000 = 256 bytes * 1001 = 512 bytes * 1010 = 1 kB * 1011 = 2 kB * 1100 = 4 kB * 1101 = 8 kB * 1110 = 16 kB * 1111 = 32 kB * * ---- ---- ---- xxxx ---- ---- ---- ---- * initial shift * * ---- ---- ---- ---- xxxx ---- ---- ---- * number of bits for table index A * * ---- ---- ---- ---- ---- xxxx ---- ---- * number of bits for table index B * * ---- ---- ---- ---- ---- ---- xxxx ---- * number of bits for table index C * * ---- ---- ---- ---- ---- ----- ---- xxxx * number of bits for table index D * */ #define TC_ENABLE_TRANSLATION 0x80000000 #define TC_ENABLE_SUPERVISOR 0x02000000 #define TC_ENABLE_FCL 0x01000000 #define TC_PS_MASK 0x00F00000 #define TC_IS_MASK 0x000F0000 #define TC_TIA_MASK 0x0000F000 #define TC_TIB_MASK 0x00000F00 #define TC_TIC_MASK 0x000000F0 #define TC_TID_MASK 0x0000000F static void mmu030_do_fake_prefetch(void) { if (currprefs.cpu_compatible) return; // fetch next opcode before MMU state switches. // There are programs that do following: // - enable MMU // - JMP (An) // "enable MMU" unmaps memory under us. TRY (prb) { uaecptr pc = m68k_getpci(); mmu030_fake_prefetch = -1; mmu030_fake_prefetch_addr = mmu030_translate(pc, regs.s != 0, false, false); mmu030_fake_prefetch = x_prefetch(0); // A26x0 ROM code switches off rom // NOP // JMP (a0) if (mmu030_fake_prefetch == 0x4e71) mmu030_fake_prefetch = x_prefetch(2); } CATCH (prb) { // didn't work, oh well.. mmu030_fake_prefetch = -1; } ENDTRY } bool mmu030_decode_tc(uae_u32 TC, bool check) { #if MMU_IPAGECACHE030 mmu030.mmu030_last_logical_address = 0xffffffff; #endif if (currprefs.mmu_ec) TC &= ~TC_ENABLE_TRANSLATION; /* Set MMU condition */ if (TC & TC_ENABLE_TRANSLATION) { if (!mmu030.enabled && check) mmu030_do_fake_prefetch(); mmu030.enabled = true; } else { if (mmu030.enabled) { mmu030_do_fake_prefetch(); write_log(_T("MMU disabled PC=%08x\n"), M68K_GETPC); } mmu030.enabled = false; return false; } /* Note: 0 = Table A, 1 = Table B, 2 = Table C, 3 = Table D */ int i, j; uae_u8 TI_bits[4] = {0,0,0,0}; /* Reset variables before extracting new values from TC */ for (i = 0; i < 4; i++) { mmu030.translation.table[i].mask = 0; mmu030.translation.table[i].shift = 0; } /* Extract initial shift and page size values from TC register */ mmu030.translation.page.size = (TC & TC_PS_MASK) >> 20; mmu030.translation.page.size3m = mmu030.translation.page.size - 3; mmu030.translation.init_shift = (TC & TC_IS_MASK) >> 16; regs.mmu_page_size = 1 << mmu030.translation.page.size; write_log(_T("68030 MMU enabled. Page size = %d PC=%08x\n"), regs.mmu_page_size, M68K_GETPC); if (mmu030.translation.page.size<8) { write_log(_T("MMU Configuration Exception: Bad value in TC register! (bad page size: %i byte)\n"), 1<>shift */ /* Get number of bits for each table index */ for (i = 0; i < 4; i++) { j = (3-i)*4; TI_bits[i] = (TC >> j) & 0xF; } /* Calculate masks and shifts for each table */ mmu030.translation.last_table = 0; uae_u8 shift = 32 - mmu030.translation.init_shift; for (i = 0; (i < 4) && TI_bits[i]; i++) { /* Get the shift */ shift -= TI_bits[i]; mmu030.translation.table[i].shift = shift; /* Build the mask */ for (j = 0; j < TI_bits[i]; j++) { mmu030.translation.table[i].mask |= (1<<(mmu030.translation.table[i].shift + j)); } /* Update until reaching the last table */ mmu030.translation.last_table = i; } #if MMU030_REG_DBG_MSG /* At least one table has to be defined using at least * 1 bit for the index. At least 2 bits are necessary * if there is no second table. If these conditions are * not met, it will automatically lead to a sum <32 * and cause an exception (see below). */ if (!TI_bits[0]) { write_log(_T("MMU Configuration Exception: Bad value in TC register! (no first table index defined)\n")); } else if ((TI_bits[0]<2) && !TI_bits[1]) { write_log(_T("MMU Configuration Exception: Bad value in TC register! (no second table index defined and)\n")); write_log(_T("MMU Configuration Exception: Bad value in TC register! (only 1 bit for first table index)\n")); } #endif /* TI fields are summed up until a zero field is reached (see above * loop). The sum of all TI field values plus page size and initial * shift has to be 32: IS + PS + TIA + TIB + TIC + TID = 32 */ if ((shift-mmu030.translation.page.size)!=0) { write_log(_T("MMU Configuration Exception: Bad value in TC register! (bad sum)\n")); Exception(56); /* MMU Configuration Exception */ return true; } #if MMU030_REG_DBG_MSG /* enable or disable debugging output */ write_log(_T("\n")); write_log(_T("TRANSLATION CONTROL: %08X\n"), TC); write_log(_T("\n")); write_log(_T("TC: translation %s\n"), (TC&TC_ENABLE_TRANSLATION ? _T("enabled") : _T("disabled"))); write_log(_T("TC: supervisor root pointer %s\n"), (TC&TC_ENABLE_SUPERVISOR ? _T("enabled") : _T("disabled"))); write_log(_T("TC: function code lookup %s\n"), (TC&TC_ENABLE_FCL ? _T("enabled") : _T("disabled"))); write_log(_T("\n")); write_log(_T("TC: Initial Shift: %i\n"), mmu030.translation.init_shift); write_log(_T("TC: Page Size: %i byte\n"), (1<> 32; if (!descriptor_type) { /* If descriptor type is invalid */ write_log(_T("MMU Configuration Exception: Root Pointer is invalid!\n")); Exception(56); /* MMU Configuration Exception */ return true; } return false; #if MMU030_REG_DBG_MSG /* enable or disable debugging output */ uae_u32 table_limit = (RP & RP_LIMIT_MASK) >> 48; uae_u32 first_addr = (RP & RP_ADDR_MASK); write_log(_T("\n")); write_log(_T("ROOT POINTER: %08X%08X\n"), (uae_u32)(RP>>32)&0xFFFFFFFF, (uae_u32)(RP&0xFFFFFFFF)); write_log(_T("\n")); write_log(_T("RP: descriptor type = %i "), descriptor_type); switch (descriptor_type) { case 0: write_log(_T("(invalid descriptor)\n")); break; case 1: write_log(_T("(early termination page descriptor)\n")); break; case 2: write_log(_T("(valid 4 byte descriptor)\n")); break; case 3: write_log(_T("(valid 8 byte descriptor)\n")); break; } write_log(_T("RP: %s limit = %i\n"), (RP&RP_LOWER_MASK) ? _T("lower") : _T("upper"), table_limit); write_log(_T("RP: first table address = %08X\n"), first_addr); write_log(_T("\n")); #endif } static void mmu030_atc_handle_history_bit(int entry_num) { int j; mmu030.atc[entry_num].mru = 1; for (j=0; j>32)&0xFFFFFFFF; descr[1] = srp_030&0xFFFFFFFF; #if MMU030_REG_DBG_MSG write_log(_T("Supervisor Root Pointer: %08X%08X\n"),descr[0],descr[1]); #endif // MMU030_REG_DBG_MSG } else { descr[0] = (crp_030>>32)&0xFFFFFFFF; descr[1] = crp_030&0xFFFFFFFF; #if MMU030_REG_DBG_MSG write_log(_T("CPU Root Pointer: %08X%08X\n"),descr[0],descr[1]); #endif } if (descr[0]&RP_ZERO_BITS) { #if MMU030_REG_DBG_MSG write_log(_T("MMU Warning: Root pointer reserved bits are non-zero! %08X\n"), descr[0]); #endif descr[0] &= (~RP_ZERO_BITS); } /* Check descriptor type of root pointer */ descr_type = descr[0]&DESCR_TYPE_MASK; switch (descr_type) { case DESCR_TYPE_INVALID: write_log(_T("Fatal error: Root pointer is invalid descriptor!\n")); mmu030.status |= MMUSR_INVALID; goto stop_search; case DESCR_TYPE_EARLY_TERM: write_log(_T("Root pointer is early termination page descriptor.\n")); early_termination = true; goto handle_page_descriptor; case DESCR_TYPE_VALID4: next_size = 4; break; case DESCR_TYPE_VALID8: next_size = 8; break; } /* If function code lookup is enabled in TC register use function code as * index for top level table, limit check not required */ if (tc_030&TC_ENABLE_FCL) { write_log(_T("Function code lookup enabled, FC = %i\n"), fc); addr_position = (descr_size==4) ? 0 : 1; table_addr = descr[addr_position]&DESCR_TD_ADDR_MASK; table_index = fc; /* table index is function code */ write_log(_T("Table FCL at %08X: index = %i, "),table_addr,table_index); /* Fetch next descriptor */ descr_num++; descr_addr[descr_num] = table_addr+(table_index*next_size); if (next_size==4) { descr[0] = phys_get_long(descr_addr[descr_num]); #if MMU030_REG_DBG_MSG write_log(_T("Next descriptor: %08X\n"),descr[0]); #endif } else { descr[0] = phys_get_long(descr_addr[descr_num]); descr[1] = phys_get_long(descr_addr[descr_num]+4); #if MMU030_REG_DBG_MSG write_log(_T("Next descriptor: %08X%08X\n"),descr[0],descr[1]); #endif } descr_size = next_size; /* Check descriptor type */ descr_type = descr[0]&DESCR_TYPE_MASK; switch (descr_type) { case DESCR_TYPE_INVALID: write_log(_T("Invalid descriptor!\n")); /* stop table walk */ mmu030.status |= MMUSR_INVALID; goto stop_search; case DESCR_TYPE_EARLY_TERM: #if MMU030_REG_DBG_MSG write_log(_T("Early termination page descriptor!\n")); #endif early_termination = true; goto handle_page_descriptor; case DESCR_TYPE_VALID4: next_size = 4; break; case DESCR_TYPE_VALID8: next_size = 8; break; } } /* Upper level tables */ do { if (descr_num) { /* if not root pointer */ /* Check protection */ if ((descr_size==8) && (descr[0]&DESCR_S) && !super) { super_violation = true; } if (descr[0]&DESCR_WP) { write_protected = true; } /* Set the updated bit */ if (!level && !(descr[0]&DESCR_U) && !super_violation) { descr[0] |= DESCR_U; phys_put_long(descr_addr[descr_num], descr[0]); } /* Update status bits */ mmu030.status |= super_violation ? MMUSR_SUPER_VIOLATION : 0; mmu030.status |= write_protected ? MMUSR_WRITE_PROTECTED : 0; /* Check if ptest level is reached */ if (level && (level==descr_num)) { goto stop_search; } } addr_position = (descr_size==4) ? 0 : 1; table_addr = descr[addr_position]&DESCR_TD_ADDR_MASK; table_index = (addr&mmu030.translation.table[t].mask)>>mmu030.translation.table[t].shift; #if MMU030_REG_DBG_MSG write_log(_T("Table %c at %08X: index = %i, "),table_letter[t],table_addr,table_index); #endif // MMU030_REG_DBG_MSG t++; /* Proceed to the next table */ /* Perform limit check */ if (descr_size==8) { limit = (descr[0]&DESCR_LIMIT_MASK)>>16; if ((descr[0]&DESCR_LOWER_MASK) && (table_indexlimit)) { mmu030.status |= (MMUSR_LIMIT_VIOLATION|MMUSR_INVALID); #if MMU030_REG_DBG_MSG write_log(_T("limit violation (upper limit %i)\n"),limit); #endif goto stop_search; } } /* Fetch next descriptor */ descr_num++; descr_addr[descr_num] = table_addr+(table_index*next_size); if (next_size==4) { descr[0] = phys_get_long(descr_addr[descr_num]); #if MMU030_REG_DBG_MSG write_log(_T("Next descriptor: %08X\n"),descr[0]); #endif } else { descr[0] = phys_get_long(descr_addr[descr_num]); descr[1] = phys_get_long(descr_addr[descr_num]+4); #if MMU030_REG_DBG_MSG write_log(_T("Next descriptor: %08X%08X\n"),descr[0],descr[1]); #endif } descr_size = next_size; /* Check descriptor type */ descr_type = descr[0]&DESCR_TYPE_MASK; switch (descr_type) { case DESCR_TYPE_INVALID: #if MMU030_REG_DBG_MSG write_log(_T("Invalid descriptor!\n")); #endif /* stop table walk */ mmu030.status |= MMUSR_INVALID; goto stop_search; case DESCR_TYPE_EARLY_TERM: /* go to last level table handling code */ if (t<=mmu030.translation.last_table) { #if MMU030_REG_DBG_MSG write_log(_T("Early termination page descriptor!\n")); #endif early_termination = true; } goto handle_page_descriptor; case DESCR_TYPE_VALID4: next_size = 4; break; case DESCR_TYPE_VALID8: next_size = 8; break; } } while (t<=mmu030.translation.last_table); /* Handle indirect descriptor */ /* Check if ptest level is reached */ if (level && (level==descr_num)) { goto stop_search; } addr_position = (descr_size==4) ? 0 : 1; indirect_addr = descr[addr_position]&DESCR_ID_ADDR_MASK; #if MMU030_REG_DBG_MSG write_log(_T("Page indirect descriptor at %08X: "),indirect_addr); #endif /* Fetch indirect descriptor */ descr_num++; descr_addr[descr_num] = indirect_addr; if (next_size==4) { descr[0] = phys_get_long(descr_addr[descr_num]); #if MMU030_REG_DBG_MSG write_log(_T("descr = %08X\n"),descr[0]); #endif } else { descr[0] = phys_get_long(descr_addr[descr_num]); descr[1] = phys_get_long(descr_addr[descr_num]+4); #if MMU030_REG_DBG_MSG write_log(_T("descr = %08X%08X"),descr[0],descr[1]); #endif } descr_size = next_size; /* Check descriptor type, only page descriptor is valid */ descr_type = descr[0]&DESCR_TYPE_MASK; if (descr_type!=DESCR_TYPE_PAGE) { mmu030.status |= MMUSR_INVALID; goto stop_search; } handle_page_descriptor: if (descr_num) { /* if not root pointer */ /* check protection */ if ((descr_size==8) && (descr[0]&DESCR_S) && !super) { super_violation = true; } if (descr[0]&DESCR_WP) { write_protected = true; } if (!level && !super_violation) { /* set modified bit */ if (!(descr[0]&DESCR_M) && write && !write_protected) { descr[0] |= DESCR_M; descr_modified = true; } /* set updated bit */ if (!(descr[0]&DESCR_U)) { descr[0] |= DESCR_U; descr_modified = true; } /* write modified descriptor if necessary */ if (descr_modified) { phys_put_long(descr_addr[descr_num], descr[0]); } } /* update status bits */ mmu030.status |= super_violation ? MMUSR_SUPER_VIOLATION : 0; mmu030.status |= write_protected ? MMUSR_WRITE_PROTECTED : 0; /* check if caching is inhibited */ cache_inhibit = (descr[0]&DESCR_CI) ? CACHE_DISABLE_MMU : CACHE_ENABLE_ALL; /* check for the modified bit and set it in the status register */ mmu030.status |= (descr[0]&DESCR_M) ? MMUSR_MODIFIED : 0; } /* Check limit using next index field of logical address. * Limit is only checked on early termination. If we are * still at root pointer level, only check limit, if FCL * is disabled. */ if (early_termination) { if (descr_num || !(tc_030&TC_ENABLE_FCL)) { if (descr_size==8) { table_index = (addr&mmu030.translation.table[t].mask)>>mmu030.translation.table[t].shift; limit = (descr[0]&DESCR_LIMIT_MASK)>>16; if ((descr[0]&DESCR_LOWER_MASK) && (table_indexlimit)) { mmu030.status |= (MMUSR_LIMIT_VIOLATION|MMUSR_INVALID); #if MMU030_REG_DBG_MSG write_log(_T("Limit violation (upper limit %i)\n"),limit); #endif goto stop_search; } } } /* Get all unused bits of the logical address table index field. * they are added to the page address */ /* TODO: They should be added via "unsigned addition". How to? */ do { unused_fields_mask |= mmu030.translation.table[t].mask; t++; } while (t<=mmu030.translation.last_table); page_addr = addr&unused_fields_mask; #if MMU030_REG_DBG_MSG write_log(_T("Logical address unused bits: %08X (mask = %08X)\n"), page_addr,unused_fields_mask); #endif } /* Get page address */ addr_position = (descr_size==4) ? 0 : 1; page_addr += (descr[addr_position]&DESCR_PD_ADDR_MASK); #if MMU030_REG_DBG_MSG write_log(_T("Page at %08X\n"),page_addr); #endif // MMU030_REG_DBG_MSG stop_search: ; /* Make compiler happy */ } CATCH(prb) { /* We jump to this place, if a bus error occurred during table search. * bBusErrorReadWrite is set in m68000.c, M68000_BusError: read = 1 */ if (bBusErrorReadWrite) { descr_num--; } mmu030.status |= (MMUSR_BUS_ERROR|MMUSR_INVALID); write_log(_T("MMU: Bus error while %s descriptor!\n"), bBusErrorReadWrite?_T("reading"):_T("writing")); } ENDTRY /* check if we have to handle ptest */ if (level) { /* Note: wp, m and sv bits are undefined if the invalid bit is set */ mmu030.status = (mmu030.status&~MMUSR_NUM_LEVELS_MASK) | descr_num; /* If root pointer is page descriptor (descr_num 0), return 0 */ return descr_num ? descr_addr[descr_num] : 0; } /* Find an ATC entry to replace */ /* Search for invalid entry */ for (i=0; i= ATC030_NUM_ENTRIES) { i = 0; write_log (_T("ATC entry not found!!!\n")); } mmu030_atc_handle_history_bit(i); /* Create ATC entry */ mmu030.atc[i].logical.addr = addr & mmu030.translation.page.imask; /* delete page index bits */ mmu030.atc[i].logical.fc = fc; mmu030.atc[i].logical.valid = true; mmu030.atc[i].physical.addr = page_addr & mmu030.translation.page.imask; /* delete page index bits */ if ((mmu030.status&MMUSR_INVALID) || (mmu030.status&MMUSR_SUPER_VIOLATION)) { mmu030.atc[i].physical.bus_error = true; } else { mmu030.atc[i].physical.bus_error = false; } mmu030.atc[i].physical.cache_inhibit = cache_inhibit; mmu030.atc[i].physical.modified = (mmu030.status&MMUSR_MODIFIED) ? true : false; mmu030.atc[i].physical.write_protect = (mmu030.status&MMUSR_WRITE_PROTECTED) ? true : false; mmu030_flush_cache(mmu030.atc[i].logical.addr); #if MMU030_ATC_DBG_MSG write_log(_T("ATC create entry(%i): logical = %08X, physical = %08X, FC = %i\n"), i, mmu030.atc[i].logical.addr, mmu030.atc[i].physical.addr, mmu030.atc[i].logical.fc); write_log(_T("ATC create entry(%i): B = %i, CI = %i, WP = %i, M = %i\n"), i, mmu030.atc[i].physical.bus_error?1:0, mmu030.atc[i].physical.cache_inhibit?1:0, mmu030.atc[i].physical.write_protect?1:0, mmu030.atc[i].physical.modified?1:0); #endif // MMU030_ATC_DBG_MSG return 0; } /* This function is used for PTEST level 0. */ void mmu030_ptest_atc_search(uaecptr logical_addr, uae_u32 fc, bool write) { int i; mmu030.status = 0; if (mmu030_match_ttr(logical_addr, fc, write)&TT_OK_MATCH) { mmu030.status |= MMUSR_TRANSP_ACCESS; return; } for (i = 0; i < ATC030_NUM_ENTRIES; i++) { if ((mmu030.atc[i].logical.fc == fc) && (mmu030.atc[i].logical.addr == logical_addr) && mmu030.atc[i].logical.valid) { break; } } if (i==ATC030_NUM_ENTRIES) { mmu030.status |= MMUSR_INVALID; return; } mmu030.status |= mmu030.atc[i].physical.bus_error ? (MMUSR_BUS_ERROR|MMUSR_INVALID) : 0; /* Note: write protect and modified bits are undefined if the invalid bit is set */ mmu030.status |= mmu030.atc[i].physical.write_protect ? MMUSR_WRITE_PROTECTED : 0; mmu030.status |= mmu030.atc[i].physical.modified ? MMUSR_MODIFIED : 0; } /* This function is used for PTEST level 1 - 7. */ uae_u32 mmu030_ptest_table_search(uaecptr logical_addr, uae_u32 fc, bool write, int level) { if (mmu030_match_ttr(logical_addr, fc, write)&TT_OK_MATCH) { return 0; } else { return mmu030_table_search(logical_addr, fc, write, level); } } /* Address Translation Cache * * The ATC uses a pseudo-least-recently-used algorithm to keep track of * least recently used entries. They are replaced if the cache is full. * An internal history-bit (MRU-bit) is used to identify these entries. * If an entry is accessed, its history-bit is set to 1. If after that * there are no more entries with zero-bits, all other history-bits are * set to 0. When no more invalid entries are in the ATC, the first entry * with a zero-bit is replaced. * * * Logical Portion (28 bit): * oooo ---- xxxx xxxx xxxx xxxx xxxx xxxx * logical address (most significant 24 bit) * * oooo -xxx ---- ---- ---- ---- ---- ---- * function code * * oooo x--- ---- ---- ---- ---- ---- ---- * valid * * * Physical Portion (28 bit): * oooo ---- xxxx xxxx xxxx xxxx xxxx xxxx * physical address * * oooo ---x ---- ---- ---- ---- ---- ---- * modified * * oooo --x- ---- ---- ---- ---- ---- ---- * write protect * * oooo -x-- ---- ---- ---- ---- ---- ---- * cache inhibit * * oooo x--- ---- ---- ---- ---- ---- ---- * bus error * */ #define ATC030_MASK 0x0FFFFFFF #define ATC030_ADDR_MASK 0x00FFFFFF /* after masking shift 8 (<< 8) */ #define ATC030_LOG_FC 0x07000000 #define ATC030_LOG_V 0x08000000 #define ATC030_PHYS_M 0x01000000 #define ATC030_PHYS_WP 0x02000000 #define ATC030_PHYS_CI 0x04000000 #define ATC030_PHYS_BE 0x08000000 void mmu030_page_fault(uaecptr addr, bool read, int flags, uae_u32 fc) { if (flags < 0) { read = (regs.mmu_ssw & MMU030_SSW_RW) ? 1 : 0; fc = regs.mmu_ssw & 7; flags = regs.mmu_ssw & ~(MMU030_SSW_FC | MMU030_SSW_RC | MMU030_SSW_FB | MMU030_SSW_RB | MMU030_SSW_RW | 7); } regs.mmu_fault_addr = addr; if (fc & 1) { regs.mmu_ssw = MMU030_SSW_DF | (MMU030_SSW_DF << 1); } else { if (currprefs.cpu_compatible) { if (regs.prefetch020_valid[1] != 1 && regs.prefetch020_valid[2] == 1) { regs.mmu_ssw = MMU030_SSW_FC | MMU030_SSW_RC; } else if (regs.prefetch020_valid[2] != 1) { regs.mmu_ssw = MMU030_SSW_FB | MMU030_SSW_RB; } else { write_log(_T("mmu030_page_fault without invalid prefetch!\n")); } } else { regs.mmu_ssw = MMU030_SSW_FB | MMU030_SSW_RB; } } regs.mmu_ssw |= read ? MMU030_SSW_RW : 0; regs.mmu_ssw |= flags; regs.mmu_ssw |= fc; bBusErrorReadWrite = read; mm030_stageb_address = addr; #if MMUDEBUG write_log(_T("MMU: page fault (logical addr=%08X SSW=%04x read=%d size=%d fc=%d pc=%08x ob=%08x ins=%04X)\n"), addr, regs.mmu_ssw, read, (flags & MMU030_SSW_SIZE_B) ? 1 : (flags & MMU030_SSW_SIZE_W) ? 2 : 4, fc, regs.instruction_pc, (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM1) ? mmu030_data_buffer : mmu030_ad[mmu030_idx].val, mmu030_opcode & 0xffff); #endif THROW(2); } static void mmu030_add_data_read_cache(uaecptr addr, uaecptr phys, uae_u32 fc) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (idx2 < MMUFASTCACHE_ENTRIES030 - 1) { atc_data_cache_read[idx2].log = idx1; atc_data_cache_read[idx2].phys = phys; } #endif } static void mmu030_add_data_write_cache(uaecptr addr, uaecptr phys, uae_u32 fc) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (idx2 < MMUFASTCACHE_ENTRIES030 - 1) { atc_data_cache_write[idx2].log = idx1; atc_data_cache_write[idx2].phys = phys; } #endif } static uaecptr mmu030_put_atc(uaecptr addr, int l, uae_u32 fc, uae_u32 size) { uae_u32 page_index = addr & mmu030.translation.page.mask; uae_u32 addr_mask = mmu030.translation.page.imask; uae_u32 physical_addr = mmu030.atc[l].physical.addr&addr_mask; #if MMU030_ATC_DBG_MSG write_log(_T("ATC match(%i): page addr = %08X, index = %08X\n"), l, physical_addr, page_index); #endif if (mmu030.atc[l].physical.bus_error || mmu030.atc[l].physical.write_protect) { mmu030_page_fault(addr, false, MMU030_SSW_SIZE_B, fc); return 0; } mmu030_cache_state = mmu030.atc[l].physical.cache_inhibit; mmu030_add_data_write_cache(addr, physical_addr, fc); return physical_addr + page_index; } static uaecptr mmu030_get_atc(uaecptr addr, int l, uae_u32 fc, uae_u32 size) { uae_u32 page_index = addr & mmu030.translation.page.mask; uae_u32 addr_mask = mmu030.translation.page.imask; uae_u32 physical_addr = mmu030.atc[l].physical.addr&addr_mask; #if MMU030_ATC_DBG_MSG write_log(_T("ATC match(%i): page addr = %08X, index = %08X\n"), l, physical_addr, page_index); #endif if (mmu030.atc[l].physical.bus_error) { mmu030_page_fault(addr, true, size, fc); return 0; } mmu030_cache_state = mmu030.atc[l].physical.cache_inhibit; mmu030_add_data_read_cache(addr, physical_addr, fc); return physical_addr + page_index; } static uaecptr mmu030_get_i_atc(uaecptr addr, int l, uae_u32 fc, uae_u32 size) { uae_u32 page_index = addr & mmu030.translation.page.mask; uae_u32 addr_mask = mmu030.translation.page.imask; uae_u32 physical_addr = mmu030.atc[l].physical.addr&addr_mask; #if MMU030_ATC_DBG_MSG write_log(_T("ATC match(%i): page addr = %08X, index = %08X\n"), l, physical_addr, page_index); #endif if (mmu030.atc[l].physical.bus_error) { mmu030_page_fault(addr, true, size, fc); return 0; } #if MMU_IPAGECACHE030 mmu030.mmu030_cache_state = mmu030.atc[l].physical.cache_inhibit; #if MMU_DIRECT_ACCESS mmu030.mmu030_last_physical_address_real = get_real_address(physical_addr); #else mmu030.mmu030_last_physical_address = physical_addr; #endif mmu030.mmu030_last_logical_address = (addr & mmu030.translation.page.imask) | fc; #endif mmu030_cache_state = mmu030.atc[l].physical.cache_inhibit; return physical_addr + page_index; } /* Generic versions of above */ static uaecptr mmu030_put_atc_generic(uaecptr addr, int l, uae_u32 fc, int flags) { uae_u32 page_index = addr & mmu030.translation.page.mask; uae_u32 addr_mask = mmu030.translation.page.imask; uae_u32 physical_addr = mmu030.atc[l].physical.addr & addr_mask; #if MMU030_ATC_DBG_MSG write_log(_T("ATC match(%i): page addr = %08X, index = %08X\n"), l, physical_addr, page_index); #endif if (mmu030.atc[l].physical.write_protect || mmu030.atc[l].physical.bus_error) { mmu030_page_fault(addr, false, flags, fc); return 0; } mmu030_add_data_write_cache(addr, physical_addr, fc); return physical_addr + page_index; } uae_u32 mmu030_get_atc_generic(uaecptr addr, int l, uae_u32 fc, int flags, bool checkwrite) { uae_u32 page_index = addr & mmu030.translation.page.mask; uae_u32 addr_mask = mmu030.translation.page.imask; uae_u32 physical_addr = mmu030.atc[l].physical.addr & addr_mask; #if MMU030_ATC_DBG_MSG write_log(_T("ATC match(%i): page addr = %08X, index = %08X\n"), l, physical_addr, page_index); #endif if (mmu030.atc[l].physical.bus_error || (checkwrite && mmu030.atc[l].physical.write_protect)) { mmu030_page_fault(addr, true, flags, fc); return 0; } mmu030_add_data_read_cache(addr, physical_addr, fc); return physical_addr + page_index; } /* This function checks if a certain logical address is in the ATC * by comparing the logical address and function code to the values * stored in the ATC entries. If a matching entry is found it sets * the history bit and returns the cache index of the entry. */ static int mmu030_logical_is_in_atc(uaecptr addr, uae_u32 fc, bool write) { uaecptr logical_addr = 0; uae_u32 addr_mask = mmu030.translation.page.imask; uae_u32 maddr = addr & addr_mask; int offset = (maddr >> mmu030.translation.page.size) & 0x1f; int i, index; index = atcindextable[offset]; for (i=0; i= ATC030_NUM_ENTRIES) index = 0; } return -1; } /* Memory access functions: * If the address matches one of the transparent translation registers * use it directly as physical address, else check ATC for the * logical address. If the logical address is not resident in the ATC * create a new ATC entry and then look up the physical address. */ STATIC_INLINE void cacheablecheck(uaecptr addr) { if (mmu030_cache_state == CACHE_ENABLE_ALL) { // MMU didn't inhibit caches, use hardware cache state mmu030_cache_state = ce_cachable[addr >> 16]; } } void mmu030_put_long(uaecptr addr, uae_u32 val, uae_u32 fc) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,true)) && mmu030.enabled) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (atc_data_cache_write[idx2].log == idx1) { addr = atc_data_cache_write[idx2].phys | (addr & mmu030.translation.page.mask); } else #endif { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); if (atc_line_num>=0) { addr = mmu030_put_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_L); } else { mmu030_table_search(addr,fc,true,0); addr = mmu030_put_atc(addr, mmu030_logical_is_in_atc(addr,fc,true), fc, MMU030_SSW_SIZE_L); } } } cacheablecheck(addr); x_phys_put_long(addr,val); } void mmu030_put_word(uaecptr addr, uae_u16 val, uae_u32 fc) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,true)) && mmu030.enabled) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (atc_data_cache_write[idx2].log == idx1) { addr = atc_data_cache_write[idx2].phys | (addr & mmu030.translation.page.mask); } else #endif { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); if (atc_line_num>=0) { addr = mmu030_put_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_W); } else { mmu030_table_search(addr, fc, true, 0); addr = mmu030_put_atc(addr, mmu030_logical_is_in_atc(addr,fc,true), fc, MMU030_SSW_SIZE_W); } } } cacheablecheck(addr); x_phys_put_word(addr,val); } void mmu030_put_byte(uaecptr addr, uae_u8 val, uae_u32 fc) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,true)) && mmu030.enabled) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (atc_data_cache_write[idx2].log == idx1) { addr = atc_data_cache_write[idx2].phys | (addr & mmu030.translation.page.mask); } else #endif { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); if (atc_line_num>=0) { addr = mmu030_put_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_B); } else { mmu030_table_search(addr, fc, true, 0); addr = mmu030_put_atc(addr, mmu030_logical_is_in_atc(addr,fc,true), fc, MMU030_SSW_SIZE_B); } } } cacheablecheck(addr); x_phys_put_byte(addr,val); } uae_u32 mmu030_get_long(uaecptr addr, uae_u32 fc) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,false)) && mmu030.enabled) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (atc_data_cache_read[idx2].log == idx1) { addr = atc_data_cache_read[idx2].phys | (addr & mmu030.translation.page.mask); } else #endif { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, false); if (atc_line_num>=0) { addr = mmu030_get_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_L); } else { mmu030_table_search(addr, fc, false, 0); addr = mmu030_get_atc(addr, mmu030_logical_is_in_atc(addr,fc,false), fc, MMU030_SSW_SIZE_L); } } } cacheablecheck(addr); return x_phys_get_long(addr); } uae_u16 mmu030_get_word(uaecptr addr, uae_u32 fc) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,false)) && mmu030.enabled) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (atc_data_cache_read[idx2].log == idx1) { addr = atc_data_cache_read[idx2].phys | (addr & mmu030.translation.page.mask); } else #endif { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, false); if (atc_line_num>=0) { addr = mmu030_get_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_W); } else { mmu030_table_search(addr, fc, false, 0); addr = mmu030_get_atc(addr, mmu030_logical_is_in_atc(addr,fc,false), fc, MMU030_SSW_SIZE_W); } } } cacheablecheck(addr); return x_phys_get_word(addr); } uae_u8 mmu030_get_byte(uaecptr addr, uae_u32 fc) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,false)) && mmu030.enabled) { #if MMU_DPAGECACHE030 uae_u32 idx1 = ((addr & mmu030.translation.page.imask) >> mmu030.translation.page.size3m) | fc; uae_u32 idx2 = idx1 & (MMUFASTCACHE_ENTRIES030 - 1); if (atc_data_cache_read[idx2].log == idx1) { addr = atc_data_cache_read[idx2].phys | (addr & mmu030.translation.page.mask); } else #endif { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, false); if (atc_line_num>=0) { addr = mmu030_get_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_B); } else { mmu030_table_search(addr, fc, false, 0); addr = mmu030_get_atc(addr, mmu030_logical_is_in_atc(addr,fc,false), fc, MMU030_SSW_SIZE_B); } } } cacheablecheck(addr); return x_phys_get_byte(addr); } uae_u32 mmu030_get_ilong(uaecptr addr, uae_u32 fc) { #if MMU_IPAGECACHE030 if (((addr & mmu030.translation.page.imask) | fc) == mmu030.mmu030_last_logical_address) { #if MMU_DIRECT_ACCESS uae_u8 *p = &mmu030.mmu030_last_physical_address_real[addr & mmu030.translation.page.mask]; return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3]); #else mmu030_cache_state = mmu030.mmu030_cache_state; return x_phys_get_ilong(mmu030.mmu030_last_physical_address + (addr & mmu030.translation.page.mask)); #endif } mmu030.mmu030_last_logical_address = 0xffffffff; #endif mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,false)) && mmu030.enabled) { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, false); if (atc_line_num >= 0) { addr = mmu030_get_i_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_L); } else { mmu030_table_search(addr, fc, false, 0); addr = mmu030_get_i_atc(addr, mmu030_logical_is_in_atc(addr, fc, false), fc, MMU030_SSW_SIZE_L); } } cacheablecheck(addr); return x_phys_get_ilong(addr); } uae_u16 mmu030_get_iword(uaecptr addr, uae_u32 fc) { #if MMU_IPAGECACHE030 if (((addr & mmu030.translation.page.imask) | fc) == mmu030.mmu030_last_logical_address) { #if MMU_DIRECT_ACCESS uae_u8 *p = &mmu030.mmu030_last_physical_address_real[addr & mmu030.translation.page.mask]; return (p[0] << 8) | p[1]; #else mmu030_cache_state = mmu030.mmu030_cache_state; return x_phys_get_iword(mmu030.mmu030_last_physical_address + (addr & mmu030.translation.page.mask)); #endif } mmu030.mmu030_last_logical_address = 0xffffffff; #endif mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,false)) && mmu030.enabled) { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, false); if (atc_line_num >= 0) { addr = mmu030_get_i_atc(addr, atc_line_num, fc, MMU030_SSW_SIZE_W); } else { mmu030_table_search(addr, fc, false, 0); addr = mmu030_get_i_atc(addr, mmu030_logical_is_in_atc(addr, fc, false), fc, MMU030_SSW_SIZE_W); } } cacheablecheck(addr); return x_phys_get_iword(addr); } /* Not commonly used access function */ static void mmu030_put_generic_lrmw(uaecptr addr, uae_u32 val, uae_u32 fc, int size, int flags) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_lrmw_ttr_access(addr,fc)) && mmu030.enabled) { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); if (atc_line_num>=0) { addr = mmu030_put_atc_generic(addr, atc_line_num, fc, flags); } else { mmu030_table_search(addr, fc, true, 0); atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); addr = mmu030_put_atc_generic(addr, atc_line_num, fc, flags); } } cacheablecheck(addr); if (size == sz_byte) x_phys_put_byte(addr, val); else if (size == sz_word) x_phys_put_word(addr, val); else x_phys_put_long(addr, val); } void mmu030_put_generic(uaecptr addr, uae_u32 val, uae_u32 fc, int size, int flags) { mmu030_cache_state = CACHE_ENABLE_ALL; if (flags & MMU030_SSW_RM) { return mmu030_put_generic_lrmw(addr, val, fc, size, flags); } if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,true)) && mmu030.enabled) { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); if (atc_line_num>=0) { addr = mmu030_put_atc_generic(addr, atc_line_num, fc, flags); } else { mmu030_table_search(addr, fc, true, 0); atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); addr = mmu030_put_atc_generic(addr, atc_line_num, fc, flags); } } cacheablecheck(addr); if (size == sz_byte) x_phys_put_byte(addr, val); else if (size == sz_word) x_phys_put_word(addr, val); else x_phys_put_long(addr, val); } static uae_u32 mmu030_get_generic_lrmw(uaecptr addr, uae_u32 fc, int size, int flags) { mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_lrmw_ttr_access(addr,fc)) && mmu030.enabled) { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); if (atc_line_num>=0) { addr = mmu030_get_atc_generic(addr, atc_line_num, fc, flags, true); } else { mmu030_table_search(addr, fc, true, 0); atc_line_num = mmu030_logical_is_in_atc(addr, fc, true); addr = mmu030_get_atc_generic(addr, atc_line_num, fc, flags, true); } } cacheablecheck(addr); if (size == sz_byte) return x_phys_get_byte(addr); else if (size == sz_word) return x_phys_get_word(addr); return x_phys_get_long(addr); } uae_u32 mmu030_get_generic(uaecptr addr, uae_u32 fc, int size, int flags) { mmu030_cache_state = CACHE_ENABLE_ALL; if (flags & MMU030_SSW_RM) { return mmu030_get_generic_lrmw(addr, fc, size, flags); } if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,false)) && mmu030.enabled) { int atc_line_num = mmu030_logical_is_in_atc(addr, fc, false); if (atc_line_num>=0) { addr = mmu030_get_atc_generic(addr, atc_line_num, fc, flags, false); } else { mmu030_table_search(addr, fc, false, 0); atc_line_num = mmu030_logical_is_in_atc(addr, fc, false); addr = mmu030_get_atc_generic(addr, atc_line_num, fc, flags, false); } } cacheablecheck(addr); if (size == sz_byte) return x_phys_get_byte(addr); else if (size == sz_word) return x_phys_get_word(addr); return x_phys_get_long(addr); } uae_u8 uae_mmu030_check_fc(uaecptr addr, bool write, uae_u32 size) { uae_u32 fc = regs.fc030; mmu030_cache_state = CACHE_ENABLE_ALL; if (fc != 7 && (!tt_enabled || !mmu030_match_ttr_access(addr,fc,write)) && mmu030.enabled) { uae_u32 flags = mmu030_size[size]; int atc_line_num = mmu030_logical_is_in_atc(addr, fc, write); if (atc_line_num>=0) { addr = mmu030_get_atc_generic(addr, atc_line_num, fc, flags, write); } else { mmu030_table_search(addr, fc, write, 0); atc_line_num = mmu030_logical_is_in_atc(addr, fc, write); addr = mmu030_get_atc_generic(addr, atc_line_num, fc, flags, false); } } // MMU inhibited if (mmu030_cache_state != CACHE_ENABLE_ALL) return mmu030_cache_state; return ce_cachable[addr >> 16]; } /* Locked RMW is rarely used */ uae_u32 uae_mmu030_get_lrmw(uaecptr addr, int size) { uae_u32 fc = (regs.s ? 4 : 0) | 1; if (size == sz_byte) { return mmu030_get_generic(addr, fc, size, MMU030_SSW_RM | MMU030_SSW_SIZE_B); } else if (size == sz_word) { if (unlikely(is_unaligned(addr, 2))) return mmu030_get_word_unaligned(addr, fc, MMU030_SSW_RM); else return mmu030_get_generic(addr, fc, size, MMU030_SSW_RM | MMU030_SSW_SIZE_W); } else { if (unlikely(is_unaligned(addr, 4))) return mmu030_get_long_unaligned(addr, fc, MMU030_SSW_RM); else return mmu030_get_generic(addr, fc, size, MMU030_SSW_RM | MMU030_SSW_SIZE_L); } } void uae_mmu030_put_lrmw(uaecptr addr, uae_u32 val, int size) { uae_u32 fc = (regs.s ? 4 : 0) | 1; if (size == sz_byte) { mmu030_put_generic(addr, val, fc, size, MMU030_SSW_RM | MMU030_SSW_SIZE_B); } else if (size == sz_word) { if (unlikely(is_unaligned(addr, 2))) mmu030_put_word_unaligned(addr, val, fc, MMU030_SSW_RM); else mmu030_put_generic(addr, val, fc, size, MMU030_SSW_RM | MMU030_SSW_SIZE_W); } else { if (unlikely(is_unaligned(addr, 4))) mmu030_put_long_unaligned(addr, val, fc, MMU030_SSW_RM); else mmu030_put_generic(addr, val, fc, size, MMU030_SSW_RM | MMU030_SSW_SIZE_L); } } uae_u16 REGPARAM2 mmu030_get_word_unaligned(uaecptr addr, uae_u32 fc, int flags) { uae_u16 res; flags |= MMU030_SSW_SIZE_W; res = (uae_u16)mmu030_get_generic(addr, fc, sz_byte, flags) << 8; SAVE_EXCEPTION; TRY(prb) { res |= mmu030_get_generic(addr + 1, fc, sz_byte, flags); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; THROW_AGAIN(prb); } ENDTRY return res; } uae_u32 REGPARAM2 mmu030_get_ilong_unaligned(uaecptr addr, uae_u32 fc, int flags) { uae_u32 res; flags |= MMU030_SSW_SIZE_L; res = (uae_u32)mmu030_get_iword(addr, fc) << 16; SAVE_EXCEPTION; TRY(prb) { res |= mmu030_get_iword(addr + 2, fc); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; THROW_AGAIN(prb); } ENDTRY return res; } uae_u32 REGPARAM2 mmu030_get_long_unaligned(uaecptr addr, uae_u32 fc, int flags) { uae_u32 res; flags |= MMU030_SSW_SIZE_L; if (likely(!(addr & 1))) { res = (uae_u32)mmu030_get_generic(addr, fc, sz_word, flags) << 16; SAVE_EXCEPTION; TRY(prb) { res |= mmu030_get_generic(addr + 2, fc, sz_word, flags); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; THROW_AGAIN(prb); } ENDTRY } else { res = (uae_u32)mmu030_get_generic(addr, fc, sz_byte, flags) << 8; SAVE_EXCEPTION; TRY(prb) { res = (res | mmu030_get_generic(addr + 1, fc, sz_byte, flags)) << 8; res = (res | mmu030_get_generic(addr + 2, fc, sz_byte, flags)) << 8; res |= mmu030_get_generic(addr + 3, fc, sz_byte, flags); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; THROW_AGAIN(prb); } ENDTRY } return res; } void REGPARAM2 mmu030_put_long_unaligned(uaecptr addr, uae_u32 val, uae_u32 fc, int flags) { flags |= MMU030_SSW_SIZE_L; SAVE_EXCEPTION; TRY(prb) { if (likely(!(addr & 1))) { mmu030_put_generic(addr, val >> 16, fc, sz_word, flags); mmu030_put_generic(addr + 2, val, fc, sz_word, flags); } else { mmu030_put_generic(addr, val >> 24, fc, sz_byte, flags); mmu030_put_generic(addr + 1, val >> 16, fc, sz_byte, flags); mmu030_put_generic(addr + 2, val >> 8, fc, sz_byte, flags); mmu030_put_generic(addr + 3, val, fc, sz_byte, flags); } RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; regs.wb3_data = val; THROW_AGAIN(prb); } ENDTRY } void REGPARAM2 mmu030_put_word_unaligned(uaecptr addr, uae_u16 val, uae_u32 fc, int flags) { flags |= MMU030_SSW_SIZE_W; SAVE_EXCEPTION; TRY(prb) { mmu030_put_generic(addr, val >> 8, fc, sz_byte, flags); mmu030_put_generic(addr + 1, val, fc, sz_byte, flags); RESTORE_EXCEPTION; } CATCH(prb) { RESTORE_EXCEPTION; regs.wb3_data = val; THROW_AGAIN(prb); } ENDTRY } /* Used by debugger */ static uaecptr mmu030_get_addr_atc(uaecptr addr, int l, uae_u32 fc, bool write) { uae_u32 page_index = addr & mmu030.translation.page.mask; uae_u32 addr_mask = mmu030.translation.page.imask; uae_u32 physical_addr = mmu030.atc[l].physical.addr&addr_mask; physical_addr += page_index; if (mmu030.atc[l].physical.bus_error || (write && mmu030.atc[l].physical.write_protect)) { mmu030_page_fault(addr, write == 0, MMU030_SSW_SIZE_B, fc); return 0; } return physical_addr; } uaecptr mmu030_translate(uaecptr addr, bool super, bool data, bool write) { int fc = (super ? 4 : 0) | (data ? 1 : 2); if ((fc==7) || (mmu030_match_ttr(addr,fc,write)&TT_OK_MATCH) || (!mmu030.enabled)) { return addr; } int atc_line_num = mmu030_logical_is_in_atc(addr, fc, write); if (atc_line_num>=0) { return mmu030_get_addr_atc(addr, atc_line_num, fc, write); } else { mmu030_table_search(addr, fc, false, 0); return mmu030_get_addr_atc(addr, mmu030_logical_is_in_atc(addr,fc,write), fc, write); } } static uae_u32 get_dcache_byte(uaecptr addr) { return read_dcache030_bget(addr, (regs.s ? 4 : 0) | 1); } static uae_u32 get_dcache_word(uaecptr addr) { return read_dcache030_wget(addr, (regs.s ? 4 : 0) | 1); } static uae_u32 get_dcache_long(uaecptr addr) { return read_dcache030_lget(addr, (regs.s ? 4 : 0) | 1); } static void put_dcache_byte(uaecptr addr, uae_u32 v) { write_dcache030_bput(addr, v, (regs.s ? 4 : 0) | 1); } static void put_dcache_word(uaecptr addr, uae_u32 v) { write_dcache030_wput(addr, v, (regs.s ? 4 : 0) | 1); } static void put_dcache_long(uaecptr addr, uae_u32 v) { write_dcache030_lput(addr, v, (regs.s ? 4 : 0) | 1); } /* MMU Reset */ void mmu030_reset(int hardreset) { /* A CPU reset causes the E-bits of TC and TT registers to be zeroed. */ mmu030.enabled = false; #if MMU_IPAGECACHE030 mmu030.mmu030_last_logical_address = 0xffffffff; #endif regs.mmu_page_size = 0; if (hardreset >= 0) { tc_030 &= ~TC_ENABLE_TRANSLATION; tt0_030 &= ~TT_ENABLE; tt1_030 &= ~TT_ENABLE; } if (hardreset > 0) { srp_030 = crp_030 = 0; tt0_030 = tt1_030 = tc_030 = 0; mmusr_030 = 0; mmu030_flush_atc_all(); } mmu030_set_funcs(); } void mmu030_set_funcs(void) { if (currprefs.mmu_model != 68030) return; if (currprefs.cpu_memory_cycle_exact) { x_phys_get_iword = mem_access_delay_wordi_read_ce020; x_phys_get_ilong = mem_access_delay_longi_read_ce020; x_phys_get_byte = mem_access_delay_byte_read_ce020; x_phys_get_word = mem_access_delay_word_read_ce020; x_phys_get_long = mem_access_delay_long_read_ce020; x_phys_put_byte = mem_access_delay_byte_write_ce020; x_phys_put_word = mem_access_delay_word_write_ce020; x_phys_put_long = mem_access_delay_long_write_ce020; } else { x_phys_get_iword = phys_get_word; x_phys_get_ilong = phys_get_long; x_phys_get_byte = phys_get_byte; x_phys_get_word = phys_get_word; x_phys_get_long = phys_get_long; x_phys_put_byte = phys_put_byte; x_phys_put_word = phys_put_word; x_phys_put_long = phys_put_long; } } void m68k_do_rte_mmu030 (uaecptr a7) { // Restore access error exception state uae_u16 format = get_word_mmu030 (a7 + 6); uae_u16 frame = format >> 12; uae_u16 ssw = get_word_mmu030 (a7 + 10); // Fetch last word, real CPU does it to allow OS bus handler to map // the page if frame crosses pages and following page is not resident. if (frame == 0xb) get_word_mmu030(a7 + 92 - 2); else get_word_mmu030(a7 + 32 - 2); // Internal register, misc flags uae_u32 ps = get_long_mmu030c(a7 + 0x28); // Internal register, our opcode storage area uae_u32 oc = get_long_mmu030c (a7 + 0x14); mmu030_opcode = (ps & 0x80000000) ? -1 : (oc & 0xffff); // Misc state data mmu030_state[0] = get_word_mmu030 (a7 + 0x30); mmu030_state[1] = get_word_mmu030 (a7 + 0x32); mmu030_state[2] = get_word_mmu030 (a7 + 0x34); mmu030_disp_store[0] = get_long_mmu030 (a7 + 0x1c); mmu030_disp_store[1] = get_long_mmu030 (a7 + 0x1c + 4); if (mmu030_state[1] & MMU030_STATEFLAG1_FMOVEM) { mmu030_fmovem_store[0] = get_long_mmu030 (a7 + 0x5c - (7 + 1) * 4); mmu030_fmovem_store[1] = get_long_mmu030 (a7 + 0x5c - (8 + 1) * 4); } // Rerun "mmu030_opcode" using restored state. mmu030_retry = true; if (frame == 0xb) { uae_u16 idxsize = get_word_mmu030 (a7 + 0x36); for (int i = 0; i < idxsize + 1; i++) { mmu030_ad[i].done = i < idxsize; mmu030_ad[i].val = get_long_mmu030 (a7 + 0x5c - (i + 1) * 4); } mmu030_ad[idxsize + 1].done = false; // did we have data fault but DF bit cleared? if (ssw & (MMU030_SSW_DF << 1) && !(ssw & MMU030_SSW_DF)) { // DF not set: mark access as done if (ssw & MMU030_SSW_RM) { // Read-Modify-Write: whole instruction is considered done write_log (_T("Read-Modify-Write and DF bit cleared! PC=%08x\n"), regs.instruction_pc); mmu030_retry = false; } else if (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM1) { // if movem, skip next move mmu030_data_buffer = get_long_mmu030 (a7 + 0x2c); mmu030_state[1] |= MMU030_STATEFLAG1_MOVEM2; } else { mmu030_ad[idxsize].done = true; if (ssw & MMU030_SSW_RW) { // Read and no DF: use value in data input buffer mmu030_data_buffer = get_long_mmu030 (a7 + 0x2c); mmu030_ad[idxsize].val = mmu030_data_buffer; } } } // did we have ins fault and RB bit cleared? if ((ssw & MMU030_SSW_FB) && !(ssw & MMU030_SSW_RB)) { uae_u16 stageb = get_word_mmu030 (a7 + 0x0e); if (mmu030_opcode == -1) { mmu030_opcode_stageb = stageb; write_log (_T("Software fixed stage B! opcode = %04x\n"), stageb); } else { mmu030_ad[idxsize].done = true; mmu030_ad[idxsize].val = stageb; write_log (_T("Software fixed stage B! opcode = %04X, opword = %04x\n"), mmu030_opcode, stageb); } } m68k_areg (regs, 7) += 92; } else { m68k_areg (regs, 7) += 32; } } void flush_mmu030 (uaecptr addr, int n) { } void m68k_do_rts_mmu030 (void) { m68k_setpc (get_long_mmu030_state (m68k_areg (regs, 7))); m68k_areg (regs, 7) += 4; } void m68k_do_bsr_mmu030 (uaecptr oldpc, uae_s32 offset) { put_long_mmu030_state (m68k_areg (regs, 7) - 4, oldpc); m68k_areg (regs, 7) -= 4; m68k_incpci (offset); } uae_u32 REGPARAM2 get_disp_ea_020_mmu030 (uae_u32 base, int idx) { uae_u16 dp; int reg; uae_u32 v; int oldidx; int pcadd = 0; // we need to do this hack here because in worst case we don't have enough // stack frame space to store two very large 020 addressing mode access state // + whatever the instruction itself does. if (mmu030_state[1] & (1 << idx)) { m68k_incpci (((mmu030_state[2] >> (idx * 4)) & 15) * 2); return mmu030_disp_store[idx]; } oldidx = mmu030_idx; dp = next_iword_mmu030_state (); pcadd += 1; reg = (dp >> 12) & 15; uae_s32 regd = regs.regs[reg]; if ((dp & 0x800) == 0) regd = (uae_s32)(uae_s16)regd; regd <<= (dp >> 9) & 3; if (dp & 0x100) { uae_s32 outer = 0; if (dp & 0x80) base = 0; if (dp & 0x40) regd = 0; if ((dp & 0x30) == 0x20) { base += (uae_s32)(uae_s16) next_iword_mmu030_state (); pcadd += 1; } if ((dp & 0x30) == 0x30) { base += next_ilong_mmu030_state (); pcadd += 2; } if ((dp & 0x3) == 0x2) { outer = (uae_s32)(uae_s16) next_iword_mmu030_state (); pcadd += 1; } if ((dp & 0x3) == 0x3) { outer = next_ilong_mmu030_state (); pcadd += 2; } if ((dp & 0x4) == 0) { base += regd; } if (dp & 0x3) { base = get_long_mmu030_state (base); } if (dp & 0x4) { base += regd; } v = base + outer; } else { v = base + (uae_s32)((uae_s8)dp) + regd; } mmu030_state[1] |= 1 << idx; mmu030_state[2] |= pcadd << (idx * 4); mmu030_disp_store[idx] = v; mmu030_idx = oldidx; mmu030_ad[mmu030_idx].done = false; return v; } // cache void m68k_do_rts_mmu030c (void) { m68k_setpc (get_long_mmu030c_state (m68k_areg (regs, 7))); m68k_areg (regs, 7) += 4; } void m68k_do_bsr_mmu030c (uaecptr oldpc, uae_s32 offset) { put_long_mmu030c_state (m68k_areg (regs, 7) - 4, oldpc); m68k_areg (regs, 7) -= 4; m68k_incpci (offset); } uae_u32 REGPARAM2 get_disp_ea_020_mmu030c (uae_u32 base, int idx) { uae_u16 dp; int reg; uae_u32 v; int oldidx; int pcadd = 0; // we need to do this hack here because in worst case we don't have enough // stack frame space to store two very large 020 addressing mode access state // + whatever the instruction itself does. if (mmu030_state[1] & (1 << idx)) { m68k_incpci (((mmu030_state[2] >> (idx * 4)) & 15) * 2); return mmu030_disp_store[idx]; } oldidx = mmu030_idx; dp = next_iword_mmu030c_state (); pcadd += 1; reg = (dp >> 12) & 15; uae_s32 regd = regs.regs[reg]; if ((dp & 0x800) == 0) regd = (uae_s32)(uae_s16)regd; regd <<= (dp >> 9) & 3; if (dp & 0x100) { uae_s32 outer = 0; if (dp & 0x80) base = 0; if (dp & 0x40) regd = 0; if ((dp & 0x30) == 0x20) { base += (uae_s32)(uae_s16) next_iword_mmu030c_state (); pcadd += 1; } if ((dp & 0x30) == 0x30) { base += next_ilong_mmu030c_state (); pcadd += 2; } if ((dp & 0x3) == 0x2) { outer = (uae_s32)(uae_s16) next_iword_mmu030c_state (); pcadd += 1; } if ((dp & 0x3) == 0x3) { outer = next_ilong_mmu030c_state (); pcadd += 2; } if ((dp & 0x4) == 0) { base += regd; } if (dp & 0x3) { base = get_long_mmu030c_state (base); } if (dp & 0x4) { base += regd; } v = base + outer; } else { v = base + (uae_s32)((uae_s8)dp) + regd; } mmu030_state[1] |= 1 << idx; mmu030_state[2] |= pcadd << (idx * 4); mmu030_disp_store[idx] = v; mmu030_idx = oldidx; mmu030_ad[mmu030_idx].done = false; return v; } void m68k_do_rte_mmu030c (uaecptr a7) { // Restore access error exception state uae_u16 format = get_word_mmu030c (a7 + 6); uae_u16 frame = format >> 12; uae_u16 ssw = get_word_mmu030c (a7 + 10); uae_u16 sr = get_word_mmu030c (a7); uae_u32 pc = get_long_mmu030c (a7 + 2); // Fetch last word, real CPU does it to allow OS bus handler to map // the page if frame crosses pages and following page is not resident. if (frame == 0xb) get_word_mmu030c(a7 + 92 - 2); else get_word_mmu030c(a7 + 32 - 2); // Internal register, misc flags uae_u32 ps = get_long_mmu030c(a7 + 0x28); // Internal register, our opcode storage area uae_u32 oc = get_long_mmu030c (a7 + 0x14); mmu030_opcode = (ps & 0x80000000) ? -1 : (oc & 0xffff); // Misc state data mmu030_state[0] = get_word_mmu030c (a7 + 0x30); mmu030_state[1] = get_word_mmu030c (a7 + 0x32); mmu030_state[2] = get_word_mmu030c (a7 + 0x34); mmu030_disp_store[0] = get_long_mmu030c (a7 + 0x1c); mmu030_disp_store[1] = get_long_mmu030c (a7 + 0x1c + 4); if (mmu030_state[1] & MMU030_STATEFLAG1_FMOVEM) { mmu030_fmovem_store[0] = get_long_mmu030c (a7 + 0x5c - (7 + 1) * 4); mmu030_fmovem_store[1] = get_long_mmu030c (a7 + 0x5c - (8 + 1) * 4); } // Rerun "mmu030_opcode" using restored state. mmu030_retry = true; if (frame == 0xb) { uae_u16 idxsize = get_word_mmu030c(a7 + 0x36); for (int i = 0; i < idxsize + 1; i++) { mmu030_ad[i].done = i < idxsize; mmu030_ad[i].val = get_long_mmu030c(a7 + 0x5c - (i + 1) * 4); } mmu030_ad[idxsize + 1].done = false; // did we have data fault but DF bit cleared? if (ssw & (MMU030_SSW_DF << 1) && !(ssw & MMU030_SSW_DF)) { // DF not set: mark access as done if (ssw & MMU030_SSW_RM) { // Read-Modify-Write: whole instruction is considered done write_log (_T("Read-Modify-Write and DF bit cleared! PC=%08x\n"), regs.instruction_pc); mmu030_retry = false; } else if (mmu030_state[1] & MMU030_STATEFLAG1_MOVEM1) { // if movem, skip next move mmu030_data_buffer = get_long_mmu030c(a7 + 0x2c); mmu030_state[1] |= MMU030_STATEFLAG1_MOVEM2; } else { mmu030_ad[idxsize].done = true; if (ssw & MMU030_SSW_RW) { // Read and no DF: use value in data input buffer mmu030_data_buffer = get_long_mmu030c(a7 + 0x2c); mmu030_ad[idxsize].val = mmu030_data_buffer; } } } regs.prefetch020_valid[0] = (ps & 1) ? 1 : 0; regs.prefetch020_valid[1] = (ps & 2) ? 1 : 0; regs.prefetch020_valid[2] = (ps & 4) ? 1 : 0; regs.pipeline_r8[0] = (ps >> 8) & 7; regs.pipeline_r8[1] = (ps >> 11) & 7; regs.pipeline_pos = (ps >> 16) & 15; regs.pipeline_stop = ((ps >> 20) & 15) == 15 ? -1 : (ps >> 20) & 15; uae_u32 stagesbc = get_long_mmu030c(a7 + 0x0c); regs.prefetch020[2] = stagesbc; regs.prefetch020[1] = stagesbc >> 16; regs.prefetch020[0] = oc >> 16; if ((ssw & MMU030_SSW_FB) && !(ssw & MMU030_SSW_RB)) { regs.prefetch020_valid[2] = 1; write_log (_T("Software fixed stage B! opcode = %04x\n"), regs.prefetch020[2]); } if ((ssw & MMU030_SSW_FC) && !(ssw & MMU030_SSW_RC)) { regs.prefetch020_valid[1] = 1; write_log (_T("Software fixed stage C! opcode = %04x\n"), regs.prefetch020[1]); } m68k_areg (regs, 7) += 92; regs.sr = sr; MakeFromSR_T0(); if (pc & 1) { exception3i (0x4E73, pc); return; } m68k_setpci (pc); if (!(ssw & (MMU030_SSW_DF << 1))) { if (!regs.prefetch020_valid[0] && regs.prefetch020_valid[2]) { // Prefetch was software fixed, continue pipeline refill fill_prefetch_030_ntx_continue(); } else if (regs.prefetch020_valid[0] && regs.prefetch020_valid[1]) { // Finished? fill_prefetch_030_ntx_continue(); } else if (mmu030_opcode == -1) { // Previous branch instruction finished successfully but its pipeline refill // step caused the exception, retry the refill, do not retry branch instruction. fill_prefetch_030_ntx(); } } } else { m68k_areg (regs, 7) += 32; } }