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
, MMUAccessType access_type
,
39 int mmu_idx
, uintptr_t retaddr
)
43 ret
= nios2_cpu_handle_mmu_fault(cs
, addr
, access_type
, mmu_idx
);
46 /* now we have a real cpu fault */
47 cpu_restore_state(cs
, retaddr
);
53 void mmu_read_debug(CPUNios2State
*env
, uint32_t rn
)
57 MMU_LOG(qemu_log("TLBACC READ %08X\n", env
->regs
[rn
]));
61 MMU_LOG(qemu_log("TLBMISC READ %08X\n", env
->regs
[rn
]));
65 MMU_LOG(qemu_log("PTEADDR READ %08X\n", env
->regs
[rn
]));
73 /* rw - 0 = read, 1 = write, 2 = fetch. */
74 unsigned int mmu_translate(CPUNios2State
*env
,
76 target_ulong vaddr
, int rw
, int mmu_idx
)
78 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
79 int pid
= (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >> 4;
80 int vpn
= vaddr
>> 12;
82 MMU_LOG(qemu_log("mmu_translate vaddr %08X, pid %08X, vpn %08X\n",
86 for (way
= 0; way
< cpu
->tlb_num_ways
; way
++) {
88 Nios2TLBEntry
*entry
=
89 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
90 (vpn
& env
->mmu
.tlb_entry_mask
)];
92 MMU_LOG(qemu_log("TLB[%d] TAG %08X, VPN %08X\n",
93 (way
* cpu
->tlb_num_ways
) +
94 (vpn
& env
->mmu
.tlb_entry_mask
),
95 entry
->tag
, (entry
->tag
>> 12)));
97 if (((entry
->tag
>> 12) != vpn
) ||
98 (((entry
->tag
& (1 << 11)) == 0) &&
99 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) != pid
))) {
102 lu
->vaddr
= vaddr
& TARGET_PAGE_MASK
;
103 lu
->paddr
= (entry
->data
& CR_TLBACC_PFN_MASK
) << TARGET_PAGE_BITS
;
104 lu
->prot
= ((entry
->data
& CR_TLBACC_R
) ? PAGE_READ
: 0) |
105 ((entry
->data
& CR_TLBACC_W
) ? PAGE_WRITE
: 0) |
106 ((entry
->data
& CR_TLBACC_X
) ? PAGE_EXEC
: 0);
108 MMU_LOG(qemu_log("HIT TLB[%d] %08X %08X %08X\n",
109 (way
* cpu
->tlb_num_ways
) +
110 (vpn
& env
->mmu
.tlb_entry_mask
),
111 lu
->vaddr
, lu
->paddr
, lu
->prot
));
117 static void mmu_flush_pid(CPUNios2State
*env
, uint32_t pid
)
119 CPUState
*cs
= ENV_GET_CPU(env
);
120 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
122 MMU_LOG(qemu_log("TLB Flush PID %d\n", pid
));
124 for (idx
= 0; idx
< cpu
->tlb_num_entries
; idx
++) {
125 Nios2TLBEntry
*entry
= &env
->mmu
.tlb
[idx
];
127 MMU_LOG(qemu_log("TLB[%d] => %08X %08X\n",
128 idx
, entry
->tag
, entry
->data
));
130 if ((entry
->tag
& (1 << 10)) && (!(entry
->tag
& (1 << 11))) &&
131 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) == pid
)) {
132 uint32_t vaddr
= entry
->tag
& TARGET_PAGE_MASK
;
134 MMU_LOG(qemu_log("TLB Flush Page %08X\n", vaddr
));
136 tlb_flush_page(cs
, vaddr
);
141 void mmu_write(CPUNios2State
*env
, uint32_t rn
, uint32_t v
)
143 CPUState
*cs
= ENV_GET_CPU(env
);
144 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
146 MMU_LOG(qemu_log("mmu_write %08X = %08X\n", rn
, v
));
150 MMU_LOG(qemu_log("TLBACC: IG %02X, FLAGS %c%c%c%c%c, PFN %05X\n",
151 v
>> CR_TLBACC_IGN_SHIFT
,
152 (v
& CR_TLBACC_C
) ? 'C' : '.',
153 (v
& CR_TLBACC_R
) ? 'R' : '.',
154 (v
& CR_TLBACC_W
) ? 'W' : '.',
155 (v
& CR_TLBACC_X
) ? 'X' : '.',
156 (v
& CR_TLBACC_G
) ? 'G' : '.',
157 v
& CR_TLBACC_PFN_MASK
));
159 /* if tlbmisc.WE == 1 then trigger a TLB write on writes to TLBACC */
160 if (env
->regs
[CR_TLBMISC
] & CR_TLBMISC_WR
) {
161 int way
= (env
->regs
[CR_TLBMISC
] >> CR_TLBMISC_WAY_SHIFT
);
162 int vpn
= (env
->mmu
.pteaddr_wr
& CR_PTEADDR_VPN_MASK
) >> 2;
163 int pid
= (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >> 4;
164 int g
= (v
& CR_TLBACC_G
) ? 1 : 0;
165 int valid
= ((vpn
& CR_TLBACC_PFN_MASK
) < 0xC0000) ? 1 : 0;
166 Nios2TLBEntry
*entry
=
167 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
168 (vpn
& env
->mmu
.tlb_entry_mask
)];
169 uint32_t newTag
= (vpn
<< 12) | (g
<< 11) | (valid
<< 10) | pid
;
170 uint32_t newData
= v
& (CR_TLBACC_C
| CR_TLBACC_R
| CR_TLBACC_W
|
171 CR_TLBACC_X
| CR_TLBACC_PFN_MASK
);
173 if ((entry
->tag
!= newTag
) || (entry
->data
!= newData
)) {
174 if (entry
->tag
& (1 << 10)) {
175 /* Flush existing entry */
176 MMU_LOG(qemu_log("TLB Flush Page (OLD) %08X\n",
177 entry
->tag
& TARGET_PAGE_MASK
));
178 tlb_flush_page(cs
, entry
->tag
& TARGET_PAGE_MASK
);
181 entry
->data
= newData
;
182 MMU_LOG(qemu_log("TLB[%d] = %08X %08X\n",
183 (way
* cpu
->tlb_num_ways
) +
184 (vpn
& env
->mmu
.tlb_entry_mask
),
185 entry
->tag
, entry
->data
));
187 /* Auto-increment tlbmisc.WAY */
188 env
->regs
[CR_TLBMISC
] =
189 (env
->regs
[CR_TLBMISC
] & ~CR_TLBMISC_WAY_MASK
) |
190 (((way
+ 1) & (cpu
->tlb_num_ways
- 1)) <<
191 CR_TLBMISC_WAY_SHIFT
);
194 /* Writes to TLBACC don't change the read-back value */
195 env
->mmu
.tlbacc_wr
= v
;
199 MMU_LOG(qemu_log("TLBMISC: WAY %X, FLAGS %c%c%c%c%c%c, PID %04X\n",
200 v
>> CR_TLBMISC_WAY_SHIFT
,
201 (v
& CR_TLBMISC_RD
) ? 'R' : '.',
202 (v
& CR_TLBMISC_WR
) ? 'W' : '.',
203 (v
& CR_TLBMISC_DBL
) ? '2' : '.',
204 (v
& CR_TLBMISC_BAD
) ? 'B' : '.',
205 (v
& CR_TLBMISC_PERM
) ? 'P' : '.',
206 (v
& CR_TLBMISC_D
) ? 'D' : '.',
207 (v
& CR_TLBMISC_PID_MASK
) >> 4));
209 if ((v
& CR_TLBMISC_PID_MASK
) !=
210 (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
)) {
211 mmu_flush_pid(env
, (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >>
212 CR_TLBMISC_PID_SHIFT
);
214 /* if tlbmisc.RD == 1 then trigger a TLB read on writes to TLBMISC */
215 if (v
& CR_TLBMISC_RD
) {
216 int way
= (v
>> CR_TLBMISC_WAY_SHIFT
);
217 int vpn
= (env
->mmu
.pteaddr_wr
& CR_PTEADDR_VPN_MASK
) >> 2;
218 Nios2TLBEntry
*entry
=
219 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
220 (vpn
& env
->mmu
.tlb_entry_mask
)];
222 env
->regs
[CR_TLBACC
] &= CR_TLBACC_IGN_MASK
;
223 env
->regs
[CR_TLBACC
] |= entry
->data
;
224 env
->regs
[CR_TLBACC
] |= (entry
->tag
& (1 << 11)) ? CR_TLBACC_G
: 0;
225 env
->regs
[CR_TLBMISC
] =
226 (v
& ~CR_TLBMISC_PID_MASK
) |
227 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) <<
228 CR_TLBMISC_PID_SHIFT
);
229 env
->regs
[CR_PTEADDR
] &= ~CR_PTEADDR_VPN_MASK
;
230 env
->regs
[CR_PTEADDR
] |= (entry
->tag
>> 12) << CR_PTEADDR_VPN_SHIFT
;
231 MMU_LOG(qemu_log("TLB READ way %d, vpn %05X, tag %08X, data %08X, "
232 "tlbacc %08X, tlbmisc %08X, pteaddr %08X\n",
233 way
, vpn
, entry
->tag
, entry
->data
,
234 env
->regs
[CR_TLBACC
], env
->regs
[CR_TLBMISC
],
235 env
->regs
[CR_PTEADDR
]));
237 env
->regs
[CR_TLBMISC
] = v
;
240 env
->mmu
.tlbmisc_wr
= v
;
244 MMU_LOG(qemu_log("PTEADDR: PTBASE %03X, VPN %05X\n",
245 v
>> CR_PTEADDR_PTBASE_SHIFT
,
246 (v
& CR_PTEADDR_VPN_MASK
) >> CR_PTEADDR_VPN_SHIFT
));
248 /* Writes to PTEADDR don't change the read-back VPN value */
249 env
->regs
[CR_PTEADDR
] = (v
& ~CR_PTEADDR_VPN_MASK
) |
250 (env
->regs
[CR_PTEADDR
] & CR_PTEADDR_VPN_MASK
);
251 env
->mmu
.pteaddr_wr
= v
;
259 void mmu_init(CPUNios2State
*env
)
261 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
262 Nios2MMU
*mmu
= &env
->mmu
;
264 MMU_LOG(qemu_log("mmu_init\n"));
266 mmu
->tlb_entry_mask
= (cpu
->tlb_num_entries
/ cpu
->tlb_num_ways
) - 1;
267 mmu
->tlb
= g_new0(Nios2TLBEntry
, cpu
->tlb_num_entries
);
270 void dump_mmu(FILE *f
, fprintf_function cpu_fprintf
, CPUNios2State
*env
)
272 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
275 cpu_fprintf(f
, "MMU: ways %d, entries %d, pid bits %d\n",
276 cpu
->tlb_num_ways
, cpu
->tlb_num_entries
,
279 for (i
= 0; i
< cpu
->tlb_num_entries
; i
++) {
280 Nios2TLBEntry
*entry
= &env
->mmu
.tlb
[i
];
281 cpu_fprintf(f
, "TLB[%d] = %08X %08X %c VPN %05X "
282 "PID %02X %c PFN %05X %c%c%c%c\n",
283 i
, entry
->tag
, entry
->data
,
284 (entry
->tag
& (1 << 10)) ? 'V' : '-',
286 entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1),
287 (entry
->tag
& (1 << 11)) ? 'G' : '-',
288 entry
->data
& CR_TLBACC_PFN_MASK
,
289 (entry
->data
& CR_TLBACC_C
) ? 'C' : '-',
290 (entry
->data
& CR_TLBACC_R
) ? 'R' : '-',
291 (entry
->data
& CR_TLBACC_W
) ? 'W' : '-',
292 (entry
->data
& CR_TLBACC_X
) ? 'X' : '-');
296 #endif /* !CONFIG_USER_ONLY */