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"
23 #include "qemu/qemu-print.h"
25 #include "exec/exec-all.h"
28 #if !defined(CONFIG_USER_ONLY)
30 /* Define this to enable MMU debug messages */
31 /* #define DEBUG_MMU */
39 void tlb_fill(CPUState
*cs
, target_ulong addr
, int size
,
40 MMUAccessType access_type
, int mmu_idx
, uintptr_t retaddr
)
44 ret
= nios2_cpu_handle_mmu_fault(cs
, addr
, size
, access_type
, mmu_idx
);
46 /* now we have a real cpu fault */
47 cpu_loop_exit_restore(cs
, retaddr
);
51 void mmu_read_debug(CPUNios2State
*env
, uint32_t rn
)
55 MMU_LOG(qemu_log("TLBACC READ %08X\n", env
->regs
[rn
]));
59 MMU_LOG(qemu_log("TLBMISC READ %08X\n", env
->regs
[rn
]));
63 MMU_LOG(qemu_log("PTEADDR READ %08X\n", env
->regs
[rn
]));
71 /* rw - 0 = read, 1 = write, 2 = fetch. */
72 unsigned int mmu_translate(CPUNios2State
*env
,
74 target_ulong vaddr
, int rw
, int mmu_idx
)
76 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
77 int pid
= (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >> 4;
78 int vpn
= vaddr
>> 12;
80 MMU_LOG(qemu_log("mmu_translate vaddr %08X, pid %08X, vpn %08X\n",
84 for (way
= 0; way
< cpu
->tlb_num_ways
; way
++) {
86 Nios2TLBEntry
*entry
=
87 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
88 (vpn
& env
->mmu
.tlb_entry_mask
)];
90 MMU_LOG(qemu_log("TLB[%d] TAG %08X, VPN %08X\n",
91 (way
* cpu
->tlb_num_ways
) +
92 (vpn
& env
->mmu
.tlb_entry_mask
),
93 entry
->tag
, (entry
->tag
>> 12)));
95 if (((entry
->tag
>> 12) != vpn
) ||
96 (((entry
->tag
& (1 << 11)) == 0) &&
97 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) != pid
))) {
100 lu
->vaddr
= vaddr
& TARGET_PAGE_MASK
;
101 lu
->paddr
= (entry
->data
& CR_TLBACC_PFN_MASK
) << TARGET_PAGE_BITS
;
102 lu
->prot
= ((entry
->data
& CR_TLBACC_R
) ? PAGE_READ
: 0) |
103 ((entry
->data
& CR_TLBACC_W
) ? PAGE_WRITE
: 0) |
104 ((entry
->data
& CR_TLBACC_X
) ? PAGE_EXEC
: 0);
106 MMU_LOG(qemu_log("HIT TLB[%d] %08X %08X %08X\n",
107 (way
* cpu
->tlb_num_ways
) +
108 (vpn
& env
->mmu
.tlb_entry_mask
),
109 lu
->vaddr
, lu
->paddr
, lu
->prot
));
115 static void mmu_flush_pid(CPUNios2State
*env
, uint32_t pid
)
117 CPUState
*cs
= ENV_GET_CPU(env
);
118 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
120 MMU_LOG(qemu_log("TLB Flush PID %d\n", pid
));
122 for (idx
= 0; idx
< cpu
->tlb_num_entries
; idx
++) {
123 Nios2TLBEntry
*entry
= &env
->mmu
.tlb
[idx
];
125 MMU_LOG(qemu_log("TLB[%d] => %08X %08X\n",
126 idx
, entry
->tag
, entry
->data
));
128 if ((entry
->tag
& (1 << 10)) && (!(entry
->tag
& (1 << 11))) &&
129 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) == pid
)) {
130 uint32_t vaddr
= entry
->tag
& TARGET_PAGE_MASK
;
132 MMU_LOG(qemu_log("TLB Flush Page %08X\n", vaddr
));
134 tlb_flush_page(cs
, vaddr
);
139 void mmu_write(CPUNios2State
*env
, uint32_t rn
, uint32_t v
)
141 CPUState
*cs
= ENV_GET_CPU(env
);
142 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
144 MMU_LOG(qemu_log("mmu_write %08X = %08X\n", rn
, v
));
148 MMU_LOG(qemu_log("TLBACC: IG %02X, FLAGS %c%c%c%c%c, PFN %05X\n",
149 v
>> CR_TLBACC_IGN_SHIFT
,
150 (v
& CR_TLBACC_C
) ? 'C' : '.',
151 (v
& CR_TLBACC_R
) ? 'R' : '.',
152 (v
& CR_TLBACC_W
) ? 'W' : '.',
153 (v
& CR_TLBACC_X
) ? 'X' : '.',
154 (v
& CR_TLBACC_G
) ? 'G' : '.',
155 v
& CR_TLBACC_PFN_MASK
));
157 /* if tlbmisc.WE == 1 then trigger a TLB write on writes to TLBACC */
158 if (env
->regs
[CR_TLBMISC
] & CR_TLBMISC_WR
) {
159 int way
= (env
->regs
[CR_TLBMISC
] >> CR_TLBMISC_WAY_SHIFT
);
160 int vpn
= (env
->mmu
.pteaddr_wr
& CR_PTEADDR_VPN_MASK
) >> 2;
161 int pid
= (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >> 4;
162 int g
= (v
& CR_TLBACC_G
) ? 1 : 0;
163 int valid
= ((vpn
& CR_TLBACC_PFN_MASK
) < 0xC0000) ? 1 : 0;
164 Nios2TLBEntry
*entry
=
165 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
166 (vpn
& env
->mmu
.tlb_entry_mask
)];
167 uint32_t newTag
= (vpn
<< 12) | (g
<< 11) | (valid
<< 10) | pid
;
168 uint32_t newData
= v
& (CR_TLBACC_C
| CR_TLBACC_R
| CR_TLBACC_W
|
169 CR_TLBACC_X
| CR_TLBACC_PFN_MASK
);
171 if ((entry
->tag
!= newTag
) || (entry
->data
!= newData
)) {
172 if (entry
->tag
& (1 << 10)) {
173 /* Flush existing entry */
174 MMU_LOG(qemu_log("TLB Flush Page (OLD) %08X\n",
175 entry
->tag
& TARGET_PAGE_MASK
));
176 tlb_flush_page(cs
, entry
->tag
& TARGET_PAGE_MASK
);
179 entry
->data
= newData
;
180 MMU_LOG(qemu_log("TLB[%d] = %08X %08X\n",
181 (way
* cpu
->tlb_num_ways
) +
182 (vpn
& env
->mmu
.tlb_entry_mask
),
183 entry
->tag
, entry
->data
));
185 /* Auto-increment tlbmisc.WAY */
186 env
->regs
[CR_TLBMISC
] =
187 (env
->regs
[CR_TLBMISC
] & ~CR_TLBMISC_WAY_MASK
) |
188 (((way
+ 1) & (cpu
->tlb_num_ways
- 1)) <<
189 CR_TLBMISC_WAY_SHIFT
);
192 /* Writes to TLBACC don't change the read-back value */
193 env
->mmu
.tlbacc_wr
= v
;
197 MMU_LOG(qemu_log("TLBMISC: WAY %X, FLAGS %c%c%c%c%c%c, PID %04X\n",
198 v
>> CR_TLBMISC_WAY_SHIFT
,
199 (v
& CR_TLBMISC_RD
) ? 'R' : '.',
200 (v
& CR_TLBMISC_WR
) ? 'W' : '.',
201 (v
& CR_TLBMISC_DBL
) ? '2' : '.',
202 (v
& CR_TLBMISC_BAD
) ? 'B' : '.',
203 (v
& CR_TLBMISC_PERM
) ? 'P' : '.',
204 (v
& CR_TLBMISC_D
) ? 'D' : '.',
205 (v
& CR_TLBMISC_PID_MASK
) >> 4));
207 if ((v
& CR_TLBMISC_PID_MASK
) !=
208 (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
)) {
209 mmu_flush_pid(env
, (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >>
210 CR_TLBMISC_PID_SHIFT
);
212 /* if tlbmisc.RD == 1 then trigger a TLB read on writes to TLBMISC */
213 if (v
& CR_TLBMISC_RD
) {
214 int way
= (v
>> CR_TLBMISC_WAY_SHIFT
);
215 int vpn
= (env
->mmu
.pteaddr_wr
& CR_PTEADDR_VPN_MASK
) >> 2;
216 Nios2TLBEntry
*entry
=
217 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
218 (vpn
& env
->mmu
.tlb_entry_mask
)];
220 env
->regs
[CR_TLBACC
] &= CR_TLBACC_IGN_MASK
;
221 env
->regs
[CR_TLBACC
] |= entry
->data
;
222 env
->regs
[CR_TLBACC
] |= (entry
->tag
& (1 << 11)) ? CR_TLBACC_G
: 0;
223 env
->regs
[CR_TLBMISC
] =
224 (v
& ~CR_TLBMISC_PID_MASK
) |
225 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) <<
226 CR_TLBMISC_PID_SHIFT
);
227 env
->regs
[CR_PTEADDR
] &= ~CR_PTEADDR_VPN_MASK
;
228 env
->regs
[CR_PTEADDR
] |= (entry
->tag
>> 12) << CR_PTEADDR_VPN_SHIFT
;
229 MMU_LOG(qemu_log("TLB READ way %d, vpn %05X, tag %08X, data %08X, "
230 "tlbacc %08X, tlbmisc %08X, pteaddr %08X\n",
231 way
, vpn
, entry
->tag
, entry
->data
,
232 env
->regs
[CR_TLBACC
], env
->regs
[CR_TLBMISC
],
233 env
->regs
[CR_PTEADDR
]));
235 env
->regs
[CR_TLBMISC
] = v
;
238 env
->mmu
.tlbmisc_wr
= v
;
242 MMU_LOG(qemu_log("PTEADDR: PTBASE %03X, VPN %05X\n",
243 v
>> CR_PTEADDR_PTBASE_SHIFT
,
244 (v
& CR_PTEADDR_VPN_MASK
) >> CR_PTEADDR_VPN_SHIFT
));
246 /* Writes to PTEADDR don't change the read-back VPN value */
247 env
->regs
[CR_PTEADDR
] = (v
& ~CR_PTEADDR_VPN_MASK
) |
248 (env
->regs
[CR_PTEADDR
] & CR_PTEADDR_VPN_MASK
);
249 env
->mmu
.pteaddr_wr
= v
;
257 void mmu_init(CPUNios2State
*env
)
259 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
260 Nios2MMU
*mmu
= &env
->mmu
;
262 MMU_LOG(qemu_log("mmu_init\n"));
264 mmu
->tlb_entry_mask
= (cpu
->tlb_num_entries
/ cpu
->tlb_num_ways
) - 1;
265 mmu
->tlb
= g_new0(Nios2TLBEntry
, cpu
->tlb_num_entries
);
268 void dump_mmu(CPUNios2State
*env
)
270 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
273 qemu_printf("MMU: ways %d, entries %d, pid bits %d\n",
274 cpu
->tlb_num_ways
, cpu
->tlb_num_entries
,
277 for (i
= 0; i
< cpu
->tlb_num_entries
; i
++) {
278 Nios2TLBEntry
*entry
= &env
->mmu
.tlb
[i
];
279 qemu_printf("TLB[%d] = %08X %08X %c VPN %05X "
280 "PID %02X %c PFN %05X %c%c%c%c\n",
281 i
, entry
->tag
, entry
->data
,
282 (entry
->tag
& (1 << 10)) ? 'V' : '-',
284 entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1),
285 (entry
->tag
& (1 << 11)) ? 'G' : '-',
286 entry
->data
& CR_TLBACC_PFN_MASK
,
287 (entry
->data
& CR_TLBACC_C
) ? 'C' : '-',
288 (entry
->data
& CR_TLBACC_R
) ? 'R' : '-',
289 (entry
->data
& CR_TLBACC_W
) ? 'W' : '-',
290 (entry
->data
& CR_TLBACC_X
) ? 'X' : '-');
294 #endif /* !CONFIG_USER_ONLY */