2 * Altera Nios II MMU emulation for qemu.
4 * Copyright (C) 2012 Chris Wulff <crwulff@gmail.com>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see
18 * <http://www.gnu.org/licenses/lgpl-2.1.html>
21 #include "qemu/osdep.h"
22 #include "qemu-common.h"
24 #include "exec/exec-all.h"
27 #if !defined(CONFIG_USER_ONLY)
29 /* Define this to enable MMU debug messages */
30 /* #define DEBUG_MMU */
38 void tlb_fill(CPUState
*cs
, target_ulong addr
, int size
,
39 MMUAccessType access_type
, int mmu_idx
, uintptr_t retaddr
)
43 ret
= nios2_cpu_handle_mmu_fault(cs
, addr
, size
, access_type
, mmu_idx
);
45 /* now we have a real cpu fault */
46 cpu_loop_exit_restore(cs
, retaddr
);
50 void mmu_read_debug(CPUNios2State
*env
, uint32_t rn
)
54 MMU_LOG(qemu_log("TLBACC READ %08X\n", env
->regs
[rn
]));
58 MMU_LOG(qemu_log("TLBMISC READ %08X\n", env
->regs
[rn
]));
62 MMU_LOG(qemu_log("PTEADDR READ %08X\n", env
->regs
[rn
]));
70 /* rw - 0 = read, 1 = write, 2 = fetch. */
71 unsigned int mmu_translate(CPUNios2State
*env
,
73 target_ulong vaddr
, int rw
, int mmu_idx
)
75 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
76 int pid
= (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >> 4;
77 int vpn
= vaddr
>> 12;
79 MMU_LOG(qemu_log("mmu_translate vaddr %08X, pid %08X, vpn %08X\n",
83 for (way
= 0; way
< cpu
->tlb_num_ways
; way
++) {
85 Nios2TLBEntry
*entry
=
86 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
87 (vpn
& env
->mmu
.tlb_entry_mask
)];
89 MMU_LOG(qemu_log("TLB[%d] TAG %08X, VPN %08X\n",
90 (way
* cpu
->tlb_num_ways
) +
91 (vpn
& env
->mmu
.tlb_entry_mask
),
92 entry
->tag
, (entry
->tag
>> 12)));
94 if (((entry
->tag
>> 12) != vpn
) ||
95 (((entry
->tag
& (1 << 11)) == 0) &&
96 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) != pid
))) {
99 lu
->vaddr
= vaddr
& TARGET_PAGE_MASK
;
100 lu
->paddr
= (entry
->data
& CR_TLBACC_PFN_MASK
) << TARGET_PAGE_BITS
;
101 lu
->prot
= ((entry
->data
& CR_TLBACC_R
) ? PAGE_READ
: 0) |
102 ((entry
->data
& CR_TLBACC_W
) ? PAGE_WRITE
: 0) |
103 ((entry
->data
& CR_TLBACC_X
) ? PAGE_EXEC
: 0);
105 MMU_LOG(qemu_log("HIT TLB[%d] %08X %08X %08X\n",
106 (way
* cpu
->tlb_num_ways
) +
107 (vpn
& env
->mmu
.tlb_entry_mask
),
108 lu
->vaddr
, lu
->paddr
, lu
->prot
));
114 static void mmu_flush_pid(CPUNios2State
*env
, uint32_t pid
)
116 CPUState
*cs
= ENV_GET_CPU(env
);
117 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
119 MMU_LOG(qemu_log("TLB Flush PID %d\n", pid
));
121 for (idx
= 0; idx
< cpu
->tlb_num_entries
; idx
++) {
122 Nios2TLBEntry
*entry
= &env
->mmu
.tlb
[idx
];
124 MMU_LOG(qemu_log("TLB[%d] => %08X %08X\n",
125 idx
, entry
->tag
, entry
->data
));
127 if ((entry
->tag
& (1 << 10)) && (!(entry
->tag
& (1 << 11))) &&
128 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) == pid
)) {
129 uint32_t vaddr
= entry
->tag
& TARGET_PAGE_MASK
;
131 MMU_LOG(qemu_log("TLB Flush Page %08X\n", vaddr
));
133 tlb_flush_page(cs
, vaddr
);
138 void mmu_write(CPUNios2State
*env
, uint32_t rn
, uint32_t v
)
140 CPUState
*cs
= ENV_GET_CPU(env
);
141 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
143 MMU_LOG(qemu_log("mmu_write %08X = %08X\n", rn
, v
));
147 MMU_LOG(qemu_log("TLBACC: IG %02X, FLAGS %c%c%c%c%c, PFN %05X\n",
148 v
>> CR_TLBACC_IGN_SHIFT
,
149 (v
& CR_TLBACC_C
) ? 'C' : '.',
150 (v
& CR_TLBACC_R
) ? 'R' : '.',
151 (v
& CR_TLBACC_W
) ? 'W' : '.',
152 (v
& CR_TLBACC_X
) ? 'X' : '.',
153 (v
& CR_TLBACC_G
) ? 'G' : '.',
154 v
& CR_TLBACC_PFN_MASK
));
156 /* if tlbmisc.WE == 1 then trigger a TLB write on writes to TLBACC */
157 if (env
->regs
[CR_TLBMISC
] & CR_TLBMISC_WR
) {
158 int way
= (env
->regs
[CR_TLBMISC
] >> CR_TLBMISC_WAY_SHIFT
);
159 int vpn
= (env
->mmu
.pteaddr_wr
& CR_PTEADDR_VPN_MASK
) >> 2;
160 int pid
= (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >> 4;
161 int g
= (v
& CR_TLBACC_G
) ? 1 : 0;
162 int valid
= ((vpn
& CR_TLBACC_PFN_MASK
) < 0xC0000) ? 1 : 0;
163 Nios2TLBEntry
*entry
=
164 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
165 (vpn
& env
->mmu
.tlb_entry_mask
)];
166 uint32_t newTag
= (vpn
<< 12) | (g
<< 11) | (valid
<< 10) | pid
;
167 uint32_t newData
= v
& (CR_TLBACC_C
| CR_TLBACC_R
| CR_TLBACC_W
|
168 CR_TLBACC_X
| CR_TLBACC_PFN_MASK
);
170 if ((entry
->tag
!= newTag
) || (entry
->data
!= newData
)) {
171 if (entry
->tag
& (1 << 10)) {
172 /* Flush existing entry */
173 MMU_LOG(qemu_log("TLB Flush Page (OLD) %08X\n",
174 entry
->tag
& TARGET_PAGE_MASK
));
175 tlb_flush_page(cs
, entry
->tag
& TARGET_PAGE_MASK
);
178 entry
->data
= newData
;
179 MMU_LOG(qemu_log("TLB[%d] = %08X %08X\n",
180 (way
* cpu
->tlb_num_ways
) +
181 (vpn
& env
->mmu
.tlb_entry_mask
),
182 entry
->tag
, entry
->data
));
184 /* Auto-increment tlbmisc.WAY */
185 env
->regs
[CR_TLBMISC
] =
186 (env
->regs
[CR_TLBMISC
] & ~CR_TLBMISC_WAY_MASK
) |
187 (((way
+ 1) & (cpu
->tlb_num_ways
- 1)) <<
188 CR_TLBMISC_WAY_SHIFT
);
191 /* Writes to TLBACC don't change the read-back value */
192 env
->mmu
.tlbacc_wr
= v
;
196 MMU_LOG(qemu_log("TLBMISC: WAY %X, FLAGS %c%c%c%c%c%c, PID %04X\n",
197 v
>> CR_TLBMISC_WAY_SHIFT
,
198 (v
& CR_TLBMISC_RD
) ? 'R' : '.',
199 (v
& CR_TLBMISC_WR
) ? 'W' : '.',
200 (v
& CR_TLBMISC_DBL
) ? '2' : '.',
201 (v
& CR_TLBMISC_BAD
) ? 'B' : '.',
202 (v
& CR_TLBMISC_PERM
) ? 'P' : '.',
203 (v
& CR_TLBMISC_D
) ? 'D' : '.',
204 (v
& CR_TLBMISC_PID_MASK
) >> 4));
206 if ((v
& CR_TLBMISC_PID_MASK
) !=
207 (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
)) {
208 mmu_flush_pid(env
, (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >>
209 CR_TLBMISC_PID_SHIFT
);
211 /* if tlbmisc.RD == 1 then trigger a TLB read on writes to TLBMISC */
212 if (v
& CR_TLBMISC_RD
) {
213 int way
= (v
>> CR_TLBMISC_WAY_SHIFT
);
214 int vpn
= (env
->mmu
.pteaddr_wr
& CR_PTEADDR_VPN_MASK
) >> 2;
215 Nios2TLBEntry
*entry
=
216 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
217 (vpn
& env
->mmu
.tlb_entry_mask
)];
219 env
->regs
[CR_TLBACC
] &= CR_TLBACC_IGN_MASK
;
220 env
->regs
[CR_TLBACC
] |= entry
->data
;
221 env
->regs
[CR_TLBACC
] |= (entry
->tag
& (1 << 11)) ? CR_TLBACC_G
: 0;
222 env
->regs
[CR_TLBMISC
] =
223 (v
& ~CR_TLBMISC_PID_MASK
) |
224 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) <<
225 CR_TLBMISC_PID_SHIFT
);
226 env
->regs
[CR_PTEADDR
] &= ~CR_PTEADDR_VPN_MASK
;
227 env
->regs
[CR_PTEADDR
] |= (entry
->tag
>> 12) << CR_PTEADDR_VPN_SHIFT
;
228 MMU_LOG(qemu_log("TLB READ way %d, vpn %05X, tag %08X, data %08X, "
229 "tlbacc %08X, tlbmisc %08X, pteaddr %08X\n",
230 way
, vpn
, entry
->tag
, entry
->data
,
231 env
->regs
[CR_TLBACC
], env
->regs
[CR_TLBMISC
],
232 env
->regs
[CR_PTEADDR
]));
234 env
->regs
[CR_TLBMISC
] = v
;
237 env
->mmu
.tlbmisc_wr
= v
;
241 MMU_LOG(qemu_log("PTEADDR: PTBASE %03X, VPN %05X\n",
242 v
>> CR_PTEADDR_PTBASE_SHIFT
,
243 (v
& CR_PTEADDR_VPN_MASK
) >> CR_PTEADDR_VPN_SHIFT
));
245 /* Writes to PTEADDR don't change the read-back VPN value */
246 env
->regs
[CR_PTEADDR
] = (v
& ~CR_PTEADDR_VPN_MASK
) |
247 (env
->regs
[CR_PTEADDR
] & CR_PTEADDR_VPN_MASK
);
248 env
->mmu
.pteaddr_wr
= v
;
256 void mmu_init(CPUNios2State
*env
)
258 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
259 Nios2MMU
*mmu
= &env
->mmu
;
261 MMU_LOG(qemu_log("mmu_init\n"));
263 mmu
->tlb_entry_mask
= (cpu
->tlb_num_entries
/ cpu
->tlb_num_ways
) - 1;
264 mmu
->tlb
= g_new0(Nios2TLBEntry
, cpu
->tlb_num_entries
);
267 void dump_mmu(FILE *f
, fprintf_function cpu_fprintf
, CPUNios2State
*env
)
269 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
272 cpu_fprintf(f
, "MMU: ways %d, entries %d, pid bits %d\n",
273 cpu
->tlb_num_ways
, cpu
->tlb_num_entries
,
276 for (i
= 0; i
< cpu
->tlb_num_entries
; i
++) {
277 Nios2TLBEntry
*entry
= &env
->mmu
.tlb
[i
];
278 cpu_fprintf(f
, "TLB[%d] = %08X %08X %c VPN %05X "
279 "PID %02X %c PFN %05X %c%c%c%c\n",
280 i
, entry
->tag
, entry
->data
,
281 (entry
->tag
& (1 << 10)) ? 'V' : '-',
283 entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1),
284 (entry
->tag
& (1 << 11)) ? 'G' : '-',
285 entry
->data
& CR_TLBACC_PFN_MASK
,
286 (entry
->data
& CR_TLBACC_C
) ? 'C' : '-',
287 (entry
->data
& CR_TLBACC_R
) ? 'R' : '-',
288 (entry
->data
& CR_TLBACC_W
) ? 'W' : '-',
289 (entry
->data
& CR_TLBACC_X
) ? 'X' : '-');
293 #endif /* !CONFIG_USER_ONLY */