aboutsummaryrefslogtreecommitdiff
path: root/riscv/mmu.cc
blob: 7ff937d377a1eae106a89c362d1fd5225b5e70a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include "mmu.h"
#include "sim.h"
#include "processor.h"

mmu_t::mmu_t(char* _mem, size_t _memsz)
 : mem(_mem), memsz(_memsz), badvaddr(0),
   ptbr(0), supervisor(true), vm_enabled(false),
   icsim(NULL), dcsim(NULL), itlbsim(NULL), dtlbsim(NULL)
{
  flush_tlb();
}

mmu_t::~mmu_t()
{
}

void mmu_t::flush_tlb()
{
  memset(tlb_insn_tag, -1, sizeof(tlb_insn_tag));
  memset(tlb_load_tag, -1, sizeof(tlb_load_tag));
  memset(tlb_store_tag, -1, sizeof(tlb_store_tag));
  flush_icache();
}

void mmu_t::flush_icache()
{
  memset(icache_tag, -1, sizeof(icache_tag));
}

void* mmu_t::refill(reg_t addr, bool store, bool fetch)
{
  reg_t idx = (addr >> PGSHIFT) % TLB_ENTRIES;
  reg_t expected_tag = addr & ~(PGSIZE-1);

  reg_t pte = walk(addr);

  reg_t pte_perm = pte & PTE_PERM;
  if(supervisor) // shift supervisor permission bits into user perm bits
    pte_perm = (pte_perm >> 3) & PTE_PERM;
  pte_perm |= pte & PTE_E;

  reg_t perm = (fetch ? PTE_UX : store ? PTE_UW : PTE_UR) | PTE_E;
  if(unlikely((pte_perm & perm) != perm))
  {
    badvaddr = addr;
    throw store ? trap_store_access_fault
        : fetch ? trap_instruction_access_fault
        :         trap_load_access_fault;
  }

  tlb_load_tag[idx] = (pte_perm & PTE_UR) ? expected_tag : -1;
  tlb_store_tag[idx] = (pte_perm & PTE_UW) ? expected_tag : -1;
  tlb_insn_tag[idx] = (pte_perm & PTE_UX) ? expected_tag : -1;
  tlb_data[idx] = (long)(pte >> PTE_PPN_SHIFT << PGSHIFT) + (long)mem;

  return (void*)(((long)addr & (PGSIZE-1)) | tlb_data[idx]);
}

pte_t mmu_t::walk(reg_t addr)
{
  pte_t pte = 0;

  if(!vm_enabled)
  {
    if(addr < memsz)
      pte = PTE_E | PTE_PERM | ((addr >> PGSHIFT) << PTE_PPN_SHIFT);
  }
  else
  {
    reg_t base = ptbr;
    reg_t ptd;

    int ptshift = (LEVELS-1)*PTIDXBITS;
    for(reg_t i = 0; i < LEVELS; i++, ptshift -= PTIDXBITS)
    {
      reg_t idx = (addr >> (PGSHIFT+ptshift)) & ((1<<PTIDXBITS)-1);

      reg_t pte_addr = base + idx*sizeof(pte_t);
      if(pte_addr >= memsz)
        break;

      ptd = *(pte_t*)(mem+pte_addr);
      if(ptd & PTE_E)
      {
        // if this PTE is from a larger PT, fake a leaf
        // PTE so the TLB will work right
        reg_t vpn = addr >> PGSHIFT;
        ptd |= (vpn & ((1<<(ptshift))-1)) << PTE_PPN_SHIFT;

        // fault if physical addr is invalid
        reg_t ppn = ptd >> PTE_PPN_SHIFT;
        if((ppn << PGSHIFT) + (addr & (PGSIZE-1)) < memsz)
          pte = ptd;
        break;
      }
      else if(!(ptd & PTE_T))
        break;

      base = (ptd >> PTE_PPN_SHIFT) << PGSHIFT;
    }
  }

  return pte;
}