Revision 12 (by dpavlin, 2007/10/06 16:45:40) make working copy
/*
 * Cisco 7200 (Predator) 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.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>

#include "rbtree.h"
#include "mips64.h"
#include "dynamips.h"
#include "memory.h"
#include "device.h"
#include "cp0.h"

/* MIPS cp0 registers names */
char *mips64_cp0_reg_names[MIPS64_CP0_REG_NR] = {
   "index" , 
   "random", 
   "entry_lo0", 
   "entry_lo1", 
   "context", 
   "pagemask",
   "wired", 
   "info",
   "badvaddr", 
   "count", 
   "entry_hi", 
   "compare", 
   "status", 
   "cause",
   "epc", 
   "prid", 
   "config", 
   "ll_addr", 
   "watch_lo", 
   "watch_hi", 
   "xcontext",
   "cp0_r21",
   "cp0_r22",
   "cp0_r23",
   "cp0_r24",
   "cp0_r25",
   "ecc", 
   "cache_err", 
   "tag_lo", 
   "tag_hi", 
   "err_epc",
   "cp0_r31",
};

/* Get cp0 register index given its name */
int cp0_get_reg_index(char *name)
{
   int i;

   for(i=0;i<MIPS64_CP0_REG_NR;i++)
      if (!strcmp(mips64_cp0_reg_names[i],name))
         return(i);

   return(-1);
}

/* Get the CPU operating mode (User,Supervisor or Kernel) - inline version */
static forced_inline u_int cp0_get_mode_inline(cpu_mips_t *cpu)
{  
   mips_cp0_t *cp0 = &cpu->cp0;
   u_int cpu_mode;

   cpu_mode = cp0->reg[MIPS_CP0_STATUS] >> MIPS_CP0_STATUS_KSU_SHIFT;
   cpu_mode &= MIPS_CP0_STATUS_KSU_MASK;
   return(cpu_mode);
}

/* Get the CPU operating mode (User,Supervisor or Kernel) */
u_int cp0_get_mode(cpu_mips_t *cpu)
{  
   return(cp0_get_mode_inline(cpu));
}

/* Check that we are running in kernel mode */
int cp0_check_kernel_mode(cpu_mips_t *cpu)
{
   u_int cpu_mode;

   cpu_mode = cp0_get_mode(cpu);

   if (cpu_mode != MIPS_CP0_STATUS_KM) {
      /* XXX Branch delay slot */
      mips64_trigger_exception(cpu,MIPS_CP0_CAUSE_ILLOP,0);
      return(1);
   }

   return(0);
}

/* Get value of random register */
static inline u_int cp0_get_random_reg(cpu_mips_t *cpu)
{
   u_int wired;

   /* We use the virtual count register as a basic "random" value */
   wired = cpu->cp0.reg[MIPS_CP0_WIRED];
   return(wired + (cpu->cp0_virt_cnt_reg % (cpu->cp0.tlb_entries - wired)));
}

/* Get a cp0 register (fast version) */
static inline m_uint64_t cp0_get_reg_fast(cpu_mips_t *cpu,u_int cp0_reg)
{
   mips_cp0_t *cp0 = &cpu->cp0;
   m_uint32_t delta,res;

   switch(cp0_reg) {
      case MIPS_CP0_COUNT:
         delta = cpu->cp0_virt_cmp_reg - cpu->cp0_virt_cnt_reg;
         res = (m_uint32_t)cp0->reg[MIPS_CP0_COMPARE];
         res -= cpu->vm->clock_divisor * delta;
         return(sign_extend(res,32));

#if 1
      case MIPS_CP0_COMPARE:
         return(sign_extend(cp0->reg[MIPS_CP0_COMPARE],32));
#else
      /* really useful and logical ? */
      case MIPS_CP0_COMPARE:
         delta = cpu->cp0_virt_cmp_reg - cpu->cp0_virt_cnt_reg;
         res = (m_uint32_t)cp0->reg[MIPS_CP0_COUNT];
         res += (cpu->vm->clock_divisor * delta);
         return(res);
#endif
      case MIPS_CP0_INFO:
         return(MIPS64_R7000_TLB64_ENABLE);

      case MIPS_CP0_RANDOM:        
         return(cp0_get_random_reg(cpu));

      default:
         return(cp0->reg[cp0_reg]);
   }
}

/* Get a cp0 register */
m_uint64_t cp0_get_reg(cpu_mips_t *cpu,u_int cp0_reg)
{
   return(cp0_get_reg_fast(cpu,cp0_reg));
}

/* Set a cp0 register */
static inline void cp0_set_reg(cpu_mips_t *cpu,u_int cp0_reg,m_uint64_t val)
{
   mips_cp0_t *cp0 = &cpu->cp0;
   m_uint32_t delta;

   switch(cp0_reg) {
      case MIPS_CP0_STATUS:
      case MIPS_CP0_CAUSE:
         cp0->reg[cp0_reg] = val;
         mips64_update_irq_flag(cpu);
         break;
         
      case MIPS_CP0_PAGEMASK:
         cp0->reg[cp0_reg] = val & MIPS_TLB_PAGE_MASK;
         break;
         
      case MIPS_CP0_COMPARE:
         mips64_clear_irq(cpu,7);
         mips64_update_irq_flag(cpu);
         cp0->reg[cp0_reg] = val;

         delta = val - cp0->reg[MIPS_CP0_COUNT];
         cpu->cp0_virt_cnt_reg = 0;
         cpu->cp0_virt_cmp_reg = delta / cpu->vm->clock_divisor;
         break;

      case MIPS_CP0_COUNT:
         cp0->reg[cp0_reg] = val;

         delta = cp0->reg[MIPS_CP0_COMPARE] - val;
         cpu->cp0_virt_cnt_reg = 0;
         cpu->cp0_virt_cmp_reg = delta / cpu->vm->clock_divisor;
         break;

     case MIPS_CP0_TLB_HI:
         cp0->reg[cp0_reg] = val & MIPS_CP0_HI_SAFE_MASK;
         break;

      case MIPS_CP0_TLB_LO_0:
      case MIPS_CP0_TLB_LO_1:
         cp0->reg[cp0_reg] = val & MIPS_CP0_LO_SAFE_MASK;
         break;

      case MIPS_CP0_RANDOM:
      case MIPS_CP0_PRID:
      case MIPS_CP0_CONFIG:
         /* read only registers */
         break;

      case MIPS_CP0_WIRED:
         cp0->reg[cp0_reg] = val & MIPS64_TLB_IDX_MASK;
         break;

      default:
         cp0->reg[cp0_reg] = val;
   }
}

/* Get a cp0 "set 1" register (R7000) */
m_uint64_t cp0_s1_get_reg(cpu_mips_t *cpu,u_int cp0_s1_reg)
{
   switch(cp0_s1_reg) {
      case MIPS_CP0_S1_CONFIG:
         return(0x7F << 25);

      case MIPS_CP0_S1_IPLLO:
         return(cpu->cp0.ipl_lo);

      case MIPS_CP0_S1_IPLHI:
         return(cpu->cp0.ipl_hi);

      case MIPS_CP0_S1_INTCTL:
         return(cpu->cp0.int_ctl);

      case MIPS_CP0_S1_DERRADDR0:
         return(cpu->cp0.derraddr0);

      case MIPS_CP0_S1_DERRADDR1:
         return(cpu->cp0.derraddr1);

      default:
         /* undefined register */
         cpu_log(cpu,"CP0_S1","trying to read unknown register %u\n",
                 cp0_s1_reg);
         return(0);
   }
}

/* Set a cp0 "set 1" register (R7000) */
static inline void cp0_s1_set_reg(cpu_mips_t *cpu,u_int cp0_s1_reg,
                                  m_uint64_t val)
{
   mips_cp0_t *cp0 = &cpu->cp0;

   switch(cp0_s1_reg) {
      case MIPS_CP0_S1_IPLLO:
         cp0->ipl_lo = val;
         break;

      case MIPS_CP0_S1_IPLHI:
         cp0->ipl_hi = val;
         break;

      case MIPS_CP0_S1_INTCTL:
         cp0->int_ctl = val;
         break;

      case MIPS_CP0_S1_DERRADDR0:
         cp0->derraddr0 = val;
         break;
         
      case MIPS_CP0_S1_DERRADDR1:
         cp0->derraddr1 = val;
         break;

      default:
         cpu_log(cpu,"CP0_S1","trying to set unknown register %u (val=0x%x)\n",
                 cp0_s1_reg,val);
   }
}

/* DMFC0 */
fastcall void cp0_exec_dmfc0(cpu_mips_t *cpu,u_int gp_reg,u_int cp0_reg)
{
   cpu->gpr[gp_reg] = cp0_get_reg_fast(cpu,cp0_reg);
}

/* DMTC0 */
fastcall void cp0_exec_dmtc0(cpu_mips_t *cpu,u_int gp_reg,u_int cp0_reg)
{
   cp0_set_reg(cpu,cp0_reg,cpu->gpr[gp_reg]);
}

/* MFC0 */
fastcall void cp0_exec_mfc0(cpu_mips_t *cpu,u_int gp_reg,u_int cp0_reg)
{
   cpu->gpr[gp_reg] = sign_extend(cp0_get_reg_fast(cpu,cp0_reg),32);
}

/* MTC0 */
fastcall void cp0_exec_mtc0(cpu_mips_t *cpu,u_int gp_reg,u_int cp0_reg)
{
   cp0_set_reg(cpu,cp0_reg,cpu->gpr[gp_reg] & 0xffffffff);
}

/* CFC0 */
fastcall void cp0_exec_cfc0(cpu_mips_t *cpu,u_int gp_reg,u_int cp0_reg)
{
   cpu->gpr[gp_reg] = sign_extend(cp0_s1_get_reg(cpu,cp0_reg),32);
}

/* CTC0 */
fastcall void cp0_exec_ctc0(cpu_mips_t *cpu,u_int gp_reg,u_int cp0_reg)
{
   cp0_s1_set_reg(cpu,cp0_reg,cpu->gpr[gp_reg] & 0xffffffff);
}

/* 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);
}

/* 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;
}

/* Get the VPN2 mask */
static forced_inline m_uint64_t cp0_get_vpn2_mask(cpu_mips_t *cpu)
{
   if (cpu->addr_mode == 64)
      return(MIPS_TLB_VPN2_MASK_64);
   else
      return(MIPS_TLB_VPN2_MASK_32);
}

/* TLB lookup */
int cp0_tlb_lookup(cpu_mips_t *cpu,m_uint64_t vaddr,mts_map_t *res)
{
   mips_cp0_t *cp0 = &cpu->cp0;
   m_uint64_t vpn_addr,vpn2_mask;
   m_uint64_t page_mask,hi_addr;
   m_uint32_t page_size,pca;
   tlb_entry_t *entry;
   u_int asid;
   int i;

   vpn2_mask = cp0_get_vpn2_mask(cpu);
   vpn_addr = vaddr & vpn2_mask;
   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 & vpn2_mask;

      if (((vpn_addr & page_mask) == hi_addr) &&
          ((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) {
            /* Even Page */
            if (entry->lo0 & MIPS_TLB_V_MASK) {
               res->vaddr = vaddr & page_mask;
               res->paddr = (entry->lo0 & MIPS_TLB_PFN_MASK) << 6;
               res->paddr &= cpu->addr_bus_mask;
               res->len   = page_size;

               pca = (entry->lo0 & MIPS_TLB_C_MASK);
               pca >>= MIPS_TLB_C_SHIFT;
               res->cached = mips64_cca_cached(pca);
            
               res->tlb_index = i;
               return(TRUE);
            }
         } else {
            /* Odd Page */
            if (entry->lo1 & MIPS_TLB_V_MASK) {
               res->vaddr = (vaddr & page_mask) + page_size;
               res->paddr = (entry->lo1 & MIPS_TLB_PFN_MASK) << 6;
               res->paddr &= cpu->addr_bus_mask;
               res->len   = page_size;

               pca = (entry->lo1 & MIPS_TLB_C_MASK);
               pca >>= MIPS_TLB_C_SHIFT;
               res->cached = mips64_cca_cached(pca);
               
               res->tlb_index = i;
               return(TRUE);
            }
         }

         /* Invalid entry */
         return(FALSE);
      }
   }

   /* No matching entry */
   return(FALSE);
}

/* 
 * Map a TLB entry into the MTS.
 *
 * We apply the physical address bus masking here.
 *
 * TODO: - Manage ASID
 *       - Manage CPU Mode (user,supervisor or kernel)
 */
void cp0_map_tlb_to_mts(cpu_mips_t *cpu,int index)
{
   m_uint64_t v0_addr,v1_addr,p0_addr,p1_addr;
   m_uint32_t page_size,pca;
   tlb_entry_t *entry;
   int cacheable;

   entry = &cpu->cp0.tlb[index];

   page_size = get_page_size(entry->mask);
   v0_addr = entry->hi & cp0_get_vpn2_mask(cpu);
   v1_addr = v0_addr + page_size;

   if (entry->lo0 & MIPS_TLB_V_MASK) {
      pca = (entry->lo0 & MIPS_TLB_C_MASK);
      pca >>= MIPS_TLB_C_SHIFT;
      cacheable = mips64_cca_cached(pca);
       
      p0_addr = (entry->lo0 & MIPS_TLB_PFN_MASK) << 6;
      cpu->mts_map(cpu,v0_addr,p0_addr & cpu->addr_bus_mask,page_size,
                   cacheable,index);
   }

   if (entry->lo1 & MIPS_TLB_V_MASK) {
      pca = (entry->lo1 & MIPS_TLB_C_MASK);
      pca >>= MIPS_TLB_C_SHIFT;
      cacheable = mips64_cca_cached(pca);

      p1_addr = (entry->lo1 & MIPS_TLB_PFN_MASK) << 6;
      cpu->mts_map(cpu,v1_addr,p1_addr & cpu->addr_bus_mask,page_size,
                   cacheable,index);
   }
}

/*
 * Unmap a TLB entry in the MTS.
 */
void cp0_unmap_tlb_to_mts(cpu_mips_t *cpu,int index)
{
   m_uint64_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 & 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);
}

/* Map all TLB entries into the MTS */
void cp0_map_all_tlb_to_mts(cpu_mips_t *cpu)
{   
   int i;

   for(i=0;i<cpu->cp0.tlb_entries;i++)
      cp0_map_tlb_to_mts(cpu,i);
}

/* TLBP: Probe a TLB entry */
fastcall void cp0_exec_tlbp(cpu_mips_t *cpu)
{
   mips_cp0_t *cp0 = &cpu->cp0;
   m_uint64_t hi_reg,asid;
   m_uint64_t vpn2,vpn2_mask;
   tlb_entry_t *entry;
   int i;
  
   vpn2_mask = 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] = 0xffffffff80000000ULL;
   
   for(i=0;i<cp0->tlb_entries;i++) {
      entry = &cp0->tlb[i];

      if (((entry->hi & vpn2_mask) == vpn2) &&
          ((entry->hi & MIPS_TLB_G_MASK) || 
           ((entry->hi & MIPS_TLB_ASID_MASK) == asid)))
      {
         cp0->reg[MIPS_CP0_INDEX] = i;
#if DEBUG_TLB_ACTIVITY
         printf("CPU: CP0_TLBP returned %u\n",i);
         tlb_dump(cpu);
#endif
      }
   }
}

/* TLBR: Read Indexed TLB entry */
fastcall void 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;

      /* 
       * The G bit must be reported in both Lo0 and Lo1 registers,
       * and cleared in Hi register.
       */
      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;
      }
   }
}

/* TLBW: Write a TLB entry */
static inline void cp0_exec_tlbw(cpu_mips_t *cpu,u_int index)
{
   mips_cp0_t *cp0 = &cpu->cp0;
   tlb_entry_t *entry;

#if DEBUG_TLB_ACTIVITY
   cpu_log(cpu,"TLB","CP0_TLBWI: writing entry %u "
           "[mask=0x%8.8llx,hi=0x%8.8llx,lo0=0x%8.8llx,lo1=0x%8.8llx]\n",
           index,cp0->reg[MIPS_CP0_PAGEMASK],cp0->reg[MIPS_CP0_TLB_HI],
           cp0->reg[MIPS_CP0_TLB_LO_0],cp0->reg[MIPS_CP0_TLB_LO_1]);
#endif

   if (index < cp0->tlb_entries)
   {
      entry = &cp0->tlb[index];

      /* Unmap the old entry if it was valid */
      cp0_unmap_tlb_to_mts(cpu,index);

      entry->mask = cp0->reg[MIPS_CP0_PAGEMASK] & MIPS_TLB_PAGE_MASK;
      entry->hi   = cp0->reg[MIPS_CP0_TLB_HI] & ~entry->mask;
      entry->hi   &= MIPS_CP0_HI_SAFE_MASK;         /* clear G bit */
      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;

      /* Inform the MTS subsystem */
      cp0_map_tlb_to_mts(cpu,index);

#if DEBUG_TLB_ACTIVITY
      tlb_dump_entry(cpu,index);
#endif
   }
}

/* TLBWI: Write Indexed TLB entry */
fastcall void cp0_exec_tlbwi(cpu_mips_t *cpu)
{
   cp0_exec_tlbw(cpu,cpu->cp0.reg[MIPS_CP0_INDEX]);
}

/* TLBWR: Write Random TLB entry */
fastcall void cp0_exec_tlbwr(cpu_mips_t *cpu)
{
   cp0_exec_tlbw(cpu,cp0_get_random_reg(cpu));
}

/* Raw dump of the TLB */
void tlb_raw_dump(cpu_mips_t *cpu)
{
   tlb_entry_t *entry;
   u_int i;

   printf("TLB dump:\n");

   for(i=0;i<cpu->cp0.tlb_entries;i++) {
      entry = &cpu->cp0.tlb[i];
      printf(" %2d: mask=0x%16.16llx hi=0x%16.16llx "
             "lo0=0x%16.16llx lo1=0x%16.16llx\n",
             i, entry->mask, entry->hi, entry->lo0, entry->lo1);
   }

   printf("\n");
}

/* Dump the specified TLB entry */
void 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.8llx ", index, entry->hi & cp0_get_vpn2_mask(cpu));

   /* global or ASID */
   if (entry->hi & MIPS_TLB_G_MASK)
      printf("(global)    ");
   else
      printf("(asid 0x%2.2llx) ",entry->hi & MIPS_TLB_ASID_MASK);

   /* 1st page: Lo0 */
   printf("p0=");

   if (entry->lo0 & MIPS_TLB_V_MASK)
      printf("0x%9.9llx",(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.9llx",(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 tlb_dump(cpu_mips_t *cpu)
{
   u_int i;

   printf("TLB dump:\n");

   for(i=0;i<cpu->cp0.tlb_entries;i++) 
      tlb_dump_entry(cpu,i);
   
   printf("\n");
}