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 mmu_read_debug(CPUNios2State
*env
, uint32_t rn
)
43 MMU_LOG(qemu_log("TLBACC READ %08X\n", env
->regs
[rn
]));
47 MMU_LOG(qemu_log("TLBMISC READ %08X\n", env
->regs
[rn
]));
51 MMU_LOG(qemu_log("PTEADDR READ %08X\n", env
->regs
[rn
]));
59 /* rw - 0 = read, 1 = write, 2 = fetch. */
60 unsigned int mmu_translate(CPUNios2State
*env
,
62 target_ulong vaddr
, int rw
, int mmu_idx
)
64 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
65 int pid
= (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >> 4;
66 int vpn
= vaddr
>> 12;
68 MMU_LOG(qemu_log("mmu_translate vaddr %08X, pid %08X, vpn %08X\n",
72 for (way
= 0; way
< cpu
->tlb_num_ways
; way
++) {
74 Nios2TLBEntry
*entry
=
75 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
76 (vpn
& env
->mmu
.tlb_entry_mask
)];
78 MMU_LOG(qemu_log("TLB[%d] TAG %08X, VPN %08X\n",
79 (way
* cpu
->tlb_num_ways
) +
80 (vpn
& env
->mmu
.tlb_entry_mask
),
81 entry
->tag
, (entry
->tag
>> 12)));
83 if (((entry
->tag
>> 12) != vpn
) ||
84 (((entry
->tag
& (1 << 11)) == 0) &&
85 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) != pid
))) {
88 lu
->vaddr
= vaddr
& TARGET_PAGE_MASK
;
89 lu
->paddr
= (entry
->data
& CR_TLBACC_PFN_MASK
) << TARGET_PAGE_BITS
;
90 lu
->prot
= ((entry
->data
& CR_TLBACC_R
) ? PAGE_READ
: 0) |
91 ((entry
->data
& CR_TLBACC_W
) ? PAGE_WRITE
: 0) |
92 ((entry
->data
& CR_TLBACC_X
) ? PAGE_EXEC
: 0);
94 MMU_LOG(qemu_log("HIT TLB[%d] %08X %08X %08X\n",
95 (way
* cpu
->tlb_num_ways
) +
96 (vpn
& env
->mmu
.tlb_entry_mask
),
97 lu
->vaddr
, lu
->paddr
, lu
->prot
));
103 static void mmu_flush_pid(CPUNios2State
*env
, uint32_t pid
)
105 CPUState
*cs
= ENV_GET_CPU(env
);
106 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
108 MMU_LOG(qemu_log("TLB Flush PID %d\n", pid
));
110 for (idx
= 0; idx
< cpu
->tlb_num_entries
; idx
++) {
111 Nios2TLBEntry
*entry
= &env
->mmu
.tlb
[idx
];
113 MMU_LOG(qemu_log("TLB[%d] => %08X %08X\n",
114 idx
, entry
->tag
, entry
->data
));
116 if ((entry
->tag
& (1 << 10)) && (!(entry
->tag
& (1 << 11))) &&
117 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) == pid
)) {
118 uint32_t vaddr
= entry
->tag
& TARGET_PAGE_MASK
;
120 MMU_LOG(qemu_log("TLB Flush Page %08X\n", vaddr
));
122 tlb_flush_page(cs
, vaddr
);
127 void mmu_write(CPUNios2State
*env
, uint32_t rn
, uint32_t v
)
129 CPUState
*cs
= ENV_GET_CPU(env
);
130 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
132 MMU_LOG(qemu_log("mmu_write %08X = %08X\n", rn
, v
));
136 MMU_LOG(qemu_log("TLBACC: IG %02X, FLAGS %c%c%c%c%c, PFN %05X\n",
137 v
>> CR_TLBACC_IGN_SHIFT
,
138 (v
& CR_TLBACC_C
) ? 'C' : '.',
139 (v
& CR_TLBACC_R
) ? 'R' : '.',
140 (v
& CR_TLBACC_W
) ? 'W' : '.',
141 (v
& CR_TLBACC_X
) ? 'X' : '.',
142 (v
& CR_TLBACC_G
) ? 'G' : '.',
143 v
& CR_TLBACC_PFN_MASK
));
145 /* if tlbmisc.WE == 1 then trigger a TLB write on writes to TLBACC */
146 if (env
->regs
[CR_TLBMISC
] & CR_TLBMISC_WR
) {
147 int way
= (env
->regs
[CR_TLBMISC
] >> CR_TLBMISC_WAY_SHIFT
);
148 int vpn
= (env
->mmu
.pteaddr_wr
& CR_PTEADDR_VPN_MASK
) >> 2;
149 int pid
= (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >> 4;
150 int g
= (v
& CR_TLBACC_G
) ? 1 : 0;
151 int valid
= ((vpn
& CR_TLBACC_PFN_MASK
) < 0xC0000) ? 1 : 0;
152 Nios2TLBEntry
*entry
=
153 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
154 (vpn
& env
->mmu
.tlb_entry_mask
)];
155 uint32_t newTag
= (vpn
<< 12) | (g
<< 11) | (valid
<< 10) | pid
;
156 uint32_t newData
= v
& (CR_TLBACC_C
| CR_TLBACC_R
| CR_TLBACC_W
|
157 CR_TLBACC_X
| CR_TLBACC_PFN_MASK
);
159 if ((entry
->tag
!= newTag
) || (entry
->data
!= newData
)) {
160 if (entry
->tag
& (1 << 10)) {
161 /* Flush existing entry */
162 MMU_LOG(qemu_log("TLB Flush Page (OLD) %08X\n",
163 entry
->tag
& TARGET_PAGE_MASK
));
164 tlb_flush_page(cs
, entry
->tag
& TARGET_PAGE_MASK
);
167 entry
->data
= newData
;
168 MMU_LOG(qemu_log("TLB[%d] = %08X %08X\n",
169 (way
* cpu
->tlb_num_ways
) +
170 (vpn
& env
->mmu
.tlb_entry_mask
),
171 entry
->tag
, entry
->data
));
173 /* Auto-increment tlbmisc.WAY */
174 env
->regs
[CR_TLBMISC
] =
175 (env
->regs
[CR_TLBMISC
] & ~CR_TLBMISC_WAY_MASK
) |
176 (((way
+ 1) & (cpu
->tlb_num_ways
- 1)) <<
177 CR_TLBMISC_WAY_SHIFT
);
180 /* Writes to TLBACC don't change the read-back value */
181 env
->mmu
.tlbacc_wr
= v
;
185 MMU_LOG(qemu_log("TLBMISC: WAY %X, FLAGS %c%c%c%c%c%c, PID %04X\n",
186 v
>> CR_TLBMISC_WAY_SHIFT
,
187 (v
& CR_TLBMISC_RD
) ? 'R' : '.',
188 (v
& CR_TLBMISC_WR
) ? 'W' : '.',
189 (v
& CR_TLBMISC_DBL
) ? '2' : '.',
190 (v
& CR_TLBMISC_BAD
) ? 'B' : '.',
191 (v
& CR_TLBMISC_PERM
) ? 'P' : '.',
192 (v
& CR_TLBMISC_D
) ? 'D' : '.',
193 (v
& CR_TLBMISC_PID_MASK
) >> 4));
195 if ((v
& CR_TLBMISC_PID_MASK
) !=
196 (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
)) {
197 mmu_flush_pid(env
, (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >>
198 CR_TLBMISC_PID_SHIFT
);
200 /* if tlbmisc.RD == 1 then trigger a TLB read on writes to TLBMISC */
201 if (v
& CR_TLBMISC_RD
) {
202 int way
= (v
>> CR_TLBMISC_WAY_SHIFT
);
203 int vpn
= (env
->mmu
.pteaddr_wr
& CR_PTEADDR_VPN_MASK
) >> 2;
204 Nios2TLBEntry
*entry
=
205 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
206 (vpn
& env
->mmu
.tlb_entry_mask
)];
208 env
->regs
[CR_TLBACC
] &= CR_TLBACC_IGN_MASK
;
209 env
->regs
[CR_TLBACC
] |= entry
->data
;
210 env
->regs
[CR_TLBACC
] |= (entry
->tag
& (1 << 11)) ? CR_TLBACC_G
: 0;
211 env
->regs
[CR_TLBMISC
] =
212 (v
& ~CR_TLBMISC_PID_MASK
) |
213 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) <<
214 CR_TLBMISC_PID_SHIFT
);
215 env
->regs
[CR_PTEADDR
] &= ~CR_PTEADDR_VPN_MASK
;
216 env
->regs
[CR_PTEADDR
] |= (entry
->tag
>> 12) << CR_PTEADDR_VPN_SHIFT
;
217 MMU_LOG(qemu_log("TLB READ way %d, vpn %05X, tag %08X, data %08X, "
218 "tlbacc %08X, tlbmisc %08X, pteaddr %08X\n",
219 way
, vpn
, entry
->tag
, entry
->data
,
220 env
->regs
[CR_TLBACC
], env
->regs
[CR_TLBMISC
],
221 env
->regs
[CR_PTEADDR
]));
223 env
->regs
[CR_TLBMISC
] = v
;
226 env
->mmu
.tlbmisc_wr
= v
;
230 MMU_LOG(qemu_log("PTEADDR: PTBASE %03X, VPN %05X\n",
231 v
>> CR_PTEADDR_PTBASE_SHIFT
,
232 (v
& CR_PTEADDR_VPN_MASK
) >> CR_PTEADDR_VPN_SHIFT
));
234 /* Writes to PTEADDR don't change the read-back VPN value */
235 env
->regs
[CR_PTEADDR
] = (v
& ~CR_PTEADDR_VPN_MASK
) |
236 (env
->regs
[CR_PTEADDR
] & CR_PTEADDR_VPN_MASK
);
237 env
->mmu
.pteaddr_wr
= v
;
245 void mmu_init(CPUNios2State
*env
)
247 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
248 Nios2MMU
*mmu
= &env
->mmu
;
250 MMU_LOG(qemu_log("mmu_init\n"));
252 mmu
->tlb_entry_mask
= (cpu
->tlb_num_entries
/ cpu
->tlb_num_ways
) - 1;
253 mmu
->tlb
= g_new0(Nios2TLBEntry
, cpu
->tlb_num_entries
);
256 void dump_mmu(CPUNios2State
*env
)
258 Nios2CPU
*cpu
= nios2_env_get_cpu(env
);
261 qemu_printf("MMU: ways %d, entries %d, pid bits %d\n",
262 cpu
->tlb_num_ways
, cpu
->tlb_num_entries
,
265 for (i
= 0; i
< cpu
->tlb_num_entries
; i
++) {
266 Nios2TLBEntry
*entry
= &env
->mmu
.tlb
[i
];
267 qemu_printf("TLB[%d] = %08X %08X %c VPN %05X "
268 "PID %02X %c PFN %05X %c%c%c%c\n",
269 i
, entry
->tag
, entry
->data
,
270 (entry
->tag
& (1 << 10)) ? 'V' : '-',
272 entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1),
273 (entry
->tag
& (1 << 11)) ? 'G' : '-',
274 entry
->data
& CR_TLBACC_PFN_MASK
,
275 (entry
->data
& CR_TLBACC_C
) ? 'C' : '-',
276 (entry
->data
& CR_TLBACC_R
) ? 'R' : '-',
277 (entry
->data
& CR_TLBACC_W
) ? 'W' : '-',
278 (entry
->data
& CR_TLBACC_X
) ? 'X' : '-');
282 #endif /* !CONFIG_USER_ONLY */