/* * Cisco router simulation platform. * Copyright (c) 2005,2006 Christophe Fillot (cf@utc.fr) * * MIPS Coprocessor 0 (System Coprocessor) implementation. * We don't use the JIT here, since there is no high performance needed. */ /* * Copyright (C) yajin 2008 * * This file is part of the virtualmips distribution. * See LICENSE file for terms of the license. * */ #define _GNU_SOURCE #include #include #include "mips.h" #include "mips_memory.h" #include "mips_cp0.h" #include "cpu.h" #include "vm.h" /* MIPS cp0 registers names */ char *mips_cp0_reg_names[MIPS64_CP0_REG_NR] = { "index", "random", "entry_lo", "cp0_r3", "context", "cp0_r5", "wired", "cp0_r7", "badvaddr", "cp0_r9", "entry_hi", "cp0_r11", "status", "cause", "epc", "prid", "dreg", "depc", "cp0_r18", "cp0_r19", "cctl", "cp0_r21", "cp0_r22", "cp0_r23", "cp0_r24", "cp0_r25", "cp0_r26", "cp0_r27", "cp0_r28", "cp0_r29", "cp0_r30", "desave", }; /* Get value of random register */ static inline u_int mips_cp0_get_random_reg (cpu_mips_t * cpu) { int random_value; random_value = (int) ((double) (cpu->cp0.tlb_entries) * rand () / (RAND_MAX + 1.0)); return random_value; } /* Get a cp0 register (fast version) */ static inline m_cp0_reg_t mips_cp0_get_reg_fast (cpu_mips_t * cpu, u_int cp0_reg, u_int sel) { mips_cp0_t *cp0 = &cpu->cp0; switch (cp0_reg) { case MIPS_CP0_RANDOM: return (mips_cp0_get_random_reg (cpu)); case MIPS_CP0_CONFIG: if (! ((1 << sel) & cp0->config_usable)) { unimpl: fprintf (stderr, "Reading unimplemented CP0 register %s\n", cp0reg_name (cp0_reg, sel)); return 0; } return cp0->config_reg[sel]; case MIPS_CP0_STATUS: switch (sel) { case 0: /* Status */ return cp0->reg[cp0_reg]; case 1: /* IntCtl */ return cp0->intctl_reg; } goto unimpl; case MIPS_CP0_PRID: switch (sel) { case 0: /* PRId */ return cp0->reg[cp0_reg]; case 1: /* EBase */ return cp0->ebase_reg; } goto unimpl; default: if (sel != 0) goto unimpl; return cp0->reg[cp0_reg]; } } /* Get a cp0 register */ m_cp0_reg_t mips_cp0_get_reg (cpu_mips_t * cpu, u_int cp0_reg) { return (mips_cp0_get_reg_fast (cpu, cp0_reg, 0)); } void fastcall mips_cp0_exec_mfc0_fastcall (cpu_mips_t * cpu, mips_insn_t insn) { int rt = bits (insn, 16, 20); int rd = bits (insn, 11, 15); int sel = bits (insn, 0, 2); cpu->reg_set (cpu, rt, sign_extend (mips_cp0_get_reg_fast (cpu, rd, sel), 32)); } /* MFC0 */ void mips_cp0_exec_mfc0 (cpu_mips_t * cpu, u_int gp_reg, u_int cp0_reg, u_int sel) { cpu->reg_set (cpu, gp_reg, sign_extend (mips_cp0_get_reg_fast (cpu, cp0_reg, sel), 32)); } void fastcall mips_cp0_exec_mtc0_fastcall (cpu_mips_t * cpu, mips_insn_t insn) { int rt = bits (insn, 16, 20); int rd = bits (insn, 11, 15); int sel = bits (insn, 0, 2); mips_cp0_set_reg (cpu, rd, sel, cpu->gpr[rt] & 0xffffffff); } void mips_cp0_exec_mtc0 (cpu_mips_t * cpu, u_int gp_reg, u_int cp0_reg, u_int sel) { mips_cp0_set_reg (cpu, cp0_reg, sel, cpu->gpr[gp_reg] & 0xffffffff); } /* Set a cp0 register */ void mips_cp0_set_reg (cpu_mips_t * cpu, u_int cp0_reg, u_int sel, m_uint32_t val) { mips_cp0_t *cp0 = &cpu->cp0; if (cpu->vm->debug_level > 2) { extern const char *cp0reg_name (unsigned cp0reg, unsigned sel); printf (" %s := %08x\n", cp0reg_name (cp0_reg, sel), val); fflush (stdout); } switch (cp0_reg) { case MIPS_CP0_STATUS: switch (sel) { case 0: /* Status */ cp0->reg[cp0_reg] = val; break; case 1: /* IntCtl */ cp0->intctl_reg = val; break; default: goto unimpl; } break; case MIPS_CP0_PRID: switch (sel) { case 0: /* PRId */ /* read only register */ break; case 1: /* EBase */ cp0->ebase_reg = (val & 0x3ffff000) | 0x80000000; break; default: goto unimpl; } break; case MIPS_CP0_RANDOM: case MIPS_CP0_WIRED: /* read only registers */ if (sel != 0) goto unimpl; break; case MIPS_CP0_COMPARE: // Write to compare will clear timer interrupt if (sel != 0) goto unimpl; clear_timer_irq (cpu); cp0->reg[cp0_reg] = val; break; case MIPS_CP0_CONFIG: if (! ((1 << sel) & cp0->config_usable)) goto unimpl; if (sel != 0) fprintf (stderr, "Writing to read only configure register sel %u\n", sel); if (sel == 0) { /* only bits 0:2 are writable */ val &= 3; cp0->config_reg[sel] &= 0xfffffffc; cp0->config_reg[sel] += val; } else cp0->config_reg[sel] = val; break; default: if (sel != 0) { unimpl: fprintf (stderr, "Writing to unimplemented CP0 register %s\n", cp0reg_name (cp0_reg, sel)); break; } cp0->reg[cp0_reg] = val; } } /* Get the VPN2 mask */ m_cp0_reg_t mips_cp0_get_vpn2_mask (cpu_mips_t * cpu) { if (cpu->addr_mode == 64) return ((m_cp0_reg_t) MIPS_TLB_VPN2_MASK_64); else return ((m_cp0_reg_t) MIPS_TLB_VPN2_MASK_32); } /* TLBP: Probe a TLB entry */ void fastcall mips_cp0_exec_tlbp (cpu_mips_t * cpu) { mips_cp0_t *cp0 = &cpu->cp0; m_uint32_t vpn2, hi_reg, vpn2_mask, page_mask, hi_addr; tlb_entry_t *entry; u_int asid; int i; vpn2_mask = mips_cp0_get_vpn2_mask (cpu); hi_reg = cp0->reg[MIPS_CP0_TLB_HI]; asid = hi_reg & MIPS_TLB_ASID_MASK; vpn2 = hi_reg & vpn2_mask; cp0->reg[MIPS_CP0_INDEX] = 0x80000000; for (i = 0; i < cp0->tlb_entries; i++) { entry = &cp0->tlb[i]; page_mask = ~(entry->mask + 0x1FFF); hi_addr = entry->hi & mips_cp0_get_vpn2_mask (cpu); if (((vpn2 & page_mask) == (hi_addr & page_mask)) && (((entry->hi & MIPS_TLB_G_MASK)) || ((entry->hi & MIPS_TLB_ASID_MASK) == asid))) { cp0->reg[MIPS_CP0_INDEX] = i; cp0->reg[MIPS_CP0_INDEX] &= ~0x80000000ULL; return; #if DEBUG_TLB_ACTIVITY cpu_log (cpu, "", "CPU: CP0_TLBP returned %x\n", i); #endif } } } /* Get the page size corresponding to a page mask */ static inline m_uint32_t get_page_size (m_uint32_t page_mask) { return ((page_mask + 0x2000) >> 1); } void mips_cp0_unmap_tlb_to_mts (cpu_mips_t * cpu, int index) { m_va_t v0_addr, v1_addr; m_uint32_t page_size; tlb_entry_t *entry; entry = &cpu->cp0.tlb[index]; page_size = get_page_size (entry->mask); v0_addr = entry->hi & mips_cp0_get_vpn2_mask (cpu); v1_addr = v0_addr + page_size; if (entry->lo0 & MIPS_TLB_V_MASK) cpu->mts_unmap (cpu, v0_addr, page_size, MTS_ACC_T, index); if (entry->lo1 & MIPS_TLB_V_MASK) cpu->mts_unmap (cpu, v1_addr, page_size, MTS_ACC_T, index); } /* TLBW: Write a TLB entry */ static forced_inline void mips_cp0_exec_tlbw (cpu_mips_t * cpu, u_int index) { mips_cp0_t *cp0 = &cpu->cp0; tlb_entry_t *entry; if (index < cp0->tlb_entries) { entry = &cp0->tlb[index]; /* Unmap the old entry if it was valid */ mips_cp0_unmap_tlb_to_mts (cpu, index); entry->mask = cp0->reg[MIPS_CP0_PAGEMASK]; entry->hi = cp0->reg[MIPS_CP0_TLB_HI]; entry->lo0 = cp0->reg[MIPS_CP0_TLB_LO_0]; entry->lo1 = cp0->reg[MIPS_CP0_TLB_LO_1]; /* if G bit is set in lo0 and lo1, set it in hi */ if ((entry->lo0 & entry->lo1) & MIPS_CP0_LO_G_MASK) entry->hi |= MIPS_TLB_G_MASK; /* Clear G bit in TLB lo0 and lo1 */ entry->lo0 &= ~MIPS_CP0_LO_G_MASK; entry->lo1 &= ~MIPS_CP0_LO_G_MASK; } } /* TLBWI: Write Indexed TLB entry */ void fastcall mips_cp0_exec_tlbwi (cpu_mips_t * cpu) { m_uint32_t index; /*FIX ME: May be a bug in tlblhandler of 2.6.11. by yajin. IN kernel, TLBL exception handler run a tlbp and then tlbw without checking tlbp is successful. assume 2aaa8a9c lw t0,(s0) 1.First we need to load instruction in 2aaa8a9c. A tlbl exception occurs. tlbp failed and tlbw write tlb entry into entry x. 2. And then exceute the instruction. load a data from a memory. A tlbl exception occurs. tlbp failed and tlbw write tlb entry into entry x. and return to pc=2aaa8a9c and do the process 1. This will cause a infinate loop. In fact, we need to check whether is successfully. If success, tlbw. otherwise tlbwr. So I first check whether last tlbp failed(check highest bit of index register). If tlbp failed, write tlbwr instead tlbw. It works well. */ if (cpu->cp0.reg[MIPS_CP0_INDEX] & 0x80000000) { mips_cp0_exec_tlbwr (cpu); } else { index = cpu->cp0.reg[MIPS_CP0_INDEX]; mips_cp0_exec_tlbw (cpu, index); } } /* TLBWR: Write Random TLB entry */ void fastcall mips_cp0_exec_tlbwr (cpu_mips_t * cpu) { mips_cp0_exec_tlbw (cpu, mips_cp0_get_random_reg (cpu)); } /* TLBR: Read Indexed TLB entry */ void fastcall mips_cp0_exec_tlbr (cpu_mips_t * cpu) { mips_cp0_t *cp0 = &cpu->cp0; tlb_entry_t *entry; u_int index; index = cp0->reg[MIPS_CP0_INDEX]; #if DEBUG_TLB_ACTIVITY cpu_log (cpu, "TLB", "CP0_TLBR: reading entry %u.\n", index); #endif if (index < cp0->tlb_entries) { entry = &cp0->tlb[index]; cp0->reg[MIPS_CP0_PAGEMASK] = entry->mask; cp0->reg[MIPS_CP0_TLB_HI] = entry->hi; cp0->reg[MIPS_CP0_TLB_LO_0] = entry->lo0; cp0->reg[MIPS_CP0_TLB_LO_1] = entry->lo1; if (entry->hi & MIPS_TLB_G_MASK) { cp0->reg[MIPS_CP0_TLB_LO_0] |= MIPS_CP0_LO_G_MASK; cp0->reg[MIPS_CP0_TLB_LO_1] |= MIPS_CP0_LO_G_MASK; cp0->reg[MIPS_CP0_TLB_HI] &= ~MIPS_TLB_G_MASK; } } } #ifndef SIM_PIC32 int mips_cp0_tlb_lookup (cpu_mips_t *cpu, m_va_t vaddr, mts_map_t *res) { mips_cp0_t *cp0 = &cpu->cp0; m_va_t vpn_addr, hi_addr, page_mask, page_size; tlb_entry_t *entry; u_int asid; int i; vpn_addr = vaddr & mips_cp0_get_vpn2_mask (cpu); asid = cp0->reg[MIPS_CP0_TLB_HI] & MIPS_TLB_ASID_MASK; for (i = 0; i < cp0->tlb_entries; i++) { entry = &cp0->tlb[i]; page_mask = ~(entry->mask + 0x1FFF); hi_addr = entry->hi & mips_cp0_get_vpn2_mask (cpu); if (((vpn_addr & page_mask) == (hi_addr & page_mask)) && ((entry->hi & MIPS_TLB_G_MASK) || ((entry->hi & MIPS_TLB_ASID_MASK) == asid))) { page_size = get_page_size (entry->mask); if ((vaddr & page_size) == 0) { res->tlb_index = i; res->vaddr = vaddr & MIPS_MIN_PAGE_MASK; res->paddr = (entry->lo0 & MIPS_TLB_PFN_MASK) << 6; res->paddr += ((res->vaddr) & (page_size - 1)); //res->paddr += ( (vaddr )& (page_size-1)); res->paddr &= cpu->addr_bus_mask; res->dirty = (entry->lo0 & MIPS_TLB_D_MASK) >> MIPS_TLB_D_SHIT; res->valid = (entry->lo0 & MIPS_TLB_V_MASK) >> MIPS_TLB_V_SHIT; res->asid = asid; res->g_bit = entry->hi & MIPS_TLB_G_MASK; return (TRUE); } else { res->tlb_index = i; res->vaddr = vaddr & MIPS_MIN_PAGE_MASK; res->paddr = (entry->lo1 & MIPS_TLB_PFN_MASK) << 6; res->paddr += ((res->vaddr) & (page_size - 1)); //res->paddr += ( (vaddr )& (page_size-1)); res->paddr &= cpu->addr_bus_mask; res->dirty = (entry->lo1 & MIPS_TLB_D_MASK) >> MIPS_TLB_D_SHIT; res->valid = (entry->lo1 & MIPS_TLB_V_MASK) >> MIPS_TLB_V_SHIT; res->asid = asid; res->g_bit = entry->hi & MIPS_TLB_G_MASK; return (TRUE); } } } return FALSE; } #endif #if 0 /* Write page size in buffer */ static char *get_page_size_str (char *buffer, size_t len, m_uint32_t page_mask) { m_uint32_t page_size; page_size = get_page_size (page_mask); /* Mb ? */ if (page_size >= (1024 * 1024)) snprintf (buffer, len, "%uMB", page_size >> 20); else snprintf (buffer, len, "%uKB", page_size >> 10); return buffer; } /* Dump the specified TLB entry */ void mips_tlb_dump_entry (cpu_mips_t * cpu, u_int index) { tlb_entry_t *entry; char buffer[256]; entry = &cpu->cp0.tlb[index]; /* virtual Address */ printf (" %2d: vaddr=0x%8.8" LL "x ", index, entry->hi & mips_cp0_get_vpn2_mask (cpu)); /* global or ASID */ if ((entry->lo0 & MIPS_TLB_G_MASK) && ((entry->lo1 & MIPS_TLB_G_MASK))) printf ("(global) "); else printf ("(asid 0x%2.2" LL "x) ", entry->hi & MIPS_TLB_ASID_MASK); /* 1st page: Lo0 */ printf ("p0="); if (entry->lo0 & MIPS_TLB_V_MASK) printf ("0x%9.9" LL "x", (entry->lo0 & MIPS_TLB_PFN_MASK) << 6); else printf ("(invalid) "); printf (" %c ", (entry->lo0 & MIPS_TLB_D_MASK) ? 'D' : ' '); /* 2nd page: Lo1 */ printf ("p1="); if (entry->lo1 & MIPS_TLB_V_MASK) printf ("0x%9.9" LL "x", (entry->lo1 & MIPS_TLB_PFN_MASK) << 6); else printf ("(invalid) "); printf (" %c ", (entry->lo1 & MIPS_TLB_D_MASK) ? 'D' : ' '); /* page size */ printf (" (%s)\n", get_page_size_str (buffer, sizeof (buffer), entry->mask)); } /* Human-Readable dump of the TLB */ void mips_tlb_dump (cpu_mips_t * cpu) { cpu_mips_t *mcpu = (cpu); u_int i; printf ("TLB dump:\n"); for (i = 0; i < mcpu->cp0.tlb_entries; i++) mips_tlb_dump_entry (mcpu, i); printf ("\n"); } /* Raw dump of the TLB */ void mips_tlb_raw_dump (cpu_mips_t * cpu) { cpu_mips_t *mcpu = (cpu); tlb_entry_t *entry; u_int i; printf ("TLB dump:\n"); for (i = 0; i < mcpu->cp0.tlb_entries; i++) { entry = &mcpu->cp0.tlb[i]; printf (" %2d: mask=0x%16.16" LL "x hi=0x%16.16" LL "x " "lo0=0x%16.16" LL "x lo1=0x%16.16" LL "x\n", i, entry->mask, entry->hi, entry->lo0, entry->lo1); } printf ("\n"); } #endif