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/qemu-print.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 mmu_read_debug(CPUNios2State
*env
, uint32_t rn
)
42 MMU_LOG(qemu_log("TLBACC READ %08X\n", env
->regs
[rn
]));
46 MMU_LOG(qemu_log("TLBMISC READ %08X\n", env
->regs
[rn
]));
50 MMU_LOG(qemu_log("PTEADDR READ %08X\n", env
->regs
[rn
]));
58 /* rw - 0 = read, 1 = write, 2 = fetch. */
59 unsigned int mmu_translate(CPUNios2State
*env
,
61 target_ulong vaddr
, int rw
, int mmu_idx
)
63 Nios2CPU
*cpu
= env_archcpu(env
);
64 int pid
= (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >> 4;
65 int vpn
= vaddr
>> 12;
67 MMU_LOG(qemu_log("mmu_translate vaddr %08X, pid %08X, vpn %08X\n",
71 for (way
= 0; way
< cpu
->tlb_num_ways
; way
++) {
73 Nios2TLBEntry
*entry
=
74 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
75 (vpn
& env
->mmu
.tlb_entry_mask
)];
77 MMU_LOG(qemu_log("TLB[%d] TAG %08X, VPN %08X\n",
78 (way
* cpu
->tlb_num_ways
) +
79 (vpn
& env
->mmu
.tlb_entry_mask
),
80 entry
->tag
, (entry
->tag
>> 12)));
82 if (((entry
->tag
>> 12) != vpn
) ||
83 (((entry
->tag
& (1 << 11)) == 0) &&
84 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) != pid
))) {
87 lu
->vaddr
= vaddr
& TARGET_PAGE_MASK
;
88 lu
->paddr
= (entry
->data
& CR_TLBACC_PFN_MASK
) << TARGET_PAGE_BITS
;
89 lu
->prot
= ((entry
->data
& CR_TLBACC_R
) ? PAGE_READ
: 0) |
90 ((entry
->data
& CR_TLBACC_W
) ? PAGE_WRITE
: 0) |
91 ((entry
->data
& CR_TLBACC_X
) ? PAGE_EXEC
: 0);
93 MMU_LOG(qemu_log("HIT TLB[%d] %08X %08X %08X\n",
94 (way
* cpu
->tlb_num_ways
) +
95 (vpn
& env
->mmu
.tlb_entry_mask
),
96 lu
->vaddr
, lu
->paddr
, lu
->prot
));
102 static void mmu_flush_pid(CPUNios2State
*env
, uint32_t pid
)
104 CPUState
*cs
= env_cpu(env
);
105 Nios2CPU
*cpu
= env_archcpu(env
);
107 MMU_LOG(qemu_log("TLB Flush PID %d\n", pid
));
109 for (idx
= 0; idx
< cpu
->tlb_num_entries
; idx
++) {
110 Nios2TLBEntry
*entry
= &env
->mmu
.tlb
[idx
];
112 MMU_LOG(qemu_log("TLB[%d] => %08X %08X\n",
113 idx
, entry
->tag
, entry
->data
));
115 if ((entry
->tag
& (1 << 10)) && (!(entry
->tag
& (1 << 11))) &&
116 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) == pid
)) {
117 uint32_t vaddr
= entry
->tag
& TARGET_PAGE_MASK
;
119 MMU_LOG(qemu_log("TLB Flush Page %08X\n", vaddr
));
121 tlb_flush_page(cs
, vaddr
);
126 void mmu_write(CPUNios2State
*env
, uint32_t rn
, uint32_t v
)
128 CPUState
*cs
= env_cpu(env
);
129 Nios2CPU
*cpu
= env_archcpu(env
);
131 MMU_LOG(qemu_log("mmu_write %08X = %08X\n", rn
, v
));
135 MMU_LOG(qemu_log("TLBACC: IG %02X, FLAGS %c%c%c%c%c, PFN %05X\n",
136 v
>> CR_TLBACC_IGN_SHIFT
,
137 (v
& CR_TLBACC_C
) ? 'C' : '.',
138 (v
& CR_TLBACC_R
) ? 'R' : '.',
139 (v
& CR_TLBACC_W
) ? 'W' : '.',
140 (v
& CR_TLBACC_X
) ? 'X' : '.',
141 (v
& CR_TLBACC_G
) ? 'G' : '.',
142 v
& CR_TLBACC_PFN_MASK
));
144 /* if tlbmisc.WE == 1 then trigger a TLB write on writes to TLBACC */
145 if (env
->regs
[CR_TLBMISC
] & CR_TLBMISC_WR
) {
146 int way
= (env
->regs
[CR_TLBMISC
] >> CR_TLBMISC_WAY_SHIFT
);
147 int vpn
= (env
->mmu
.pteaddr_wr
& CR_PTEADDR_VPN_MASK
) >> 2;
148 int pid
= (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >> 4;
149 int g
= (v
& CR_TLBACC_G
) ? 1 : 0;
150 int valid
= ((vpn
& CR_TLBACC_PFN_MASK
) < 0xC0000) ? 1 : 0;
151 Nios2TLBEntry
*entry
=
152 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
153 (vpn
& env
->mmu
.tlb_entry_mask
)];
154 uint32_t newTag
= (vpn
<< 12) | (g
<< 11) | (valid
<< 10) | pid
;
155 uint32_t newData
= v
& (CR_TLBACC_C
| CR_TLBACC_R
| CR_TLBACC_W
|
156 CR_TLBACC_X
| CR_TLBACC_PFN_MASK
);
158 if ((entry
->tag
!= newTag
) || (entry
->data
!= newData
)) {
159 if (entry
->tag
& (1 << 10)) {
160 /* Flush existing entry */
161 MMU_LOG(qemu_log("TLB Flush Page (OLD) %08X\n",
162 entry
->tag
& TARGET_PAGE_MASK
));
163 tlb_flush_page(cs
, entry
->tag
& TARGET_PAGE_MASK
);
166 entry
->data
= newData
;
167 MMU_LOG(qemu_log("TLB[%d] = %08X %08X\n",
168 (way
* cpu
->tlb_num_ways
) +
169 (vpn
& env
->mmu
.tlb_entry_mask
),
170 entry
->tag
, entry
->data
));
172 /* Auto-increment tlbmisc.WAY */
173 env
->regs
[CR_TLBMISC
] =
174 (env
->regs
[CR_TLBMISC
] & ~CR_TLBMISC_WAY_MASK
) |
175 (((way
+ 1) & (cpu
->tlb_num_ways
- 1)) <<
176 CR_TLBMISC_WAY_SHIFT
);
179 /* Writes to TLBACC don't change the read-back value */
180 env
->mmu
.tlbacc_wr
= v
;
184 MMU_LOG(qemu_log("TLBMISC: WAY %X, FLAGS %c%c%c%c%c%c, PID %04X\n",
185 v
>> CR_TLBMISC_WAY_SHIFT
,
186 (v
& CR_TLBMISC_RD
) ? 'R' : '.',
187 (v
& CR_TLBMISC_WR
) ? 'W' : '.',
188 (v
& CR_TLBMISC_DBL
) ? '2' : '.',
189 (v
& CR_TLBMISC_BAD
) ? 'B' : '.',
190 (v
& CR_TLBMISC_PERM
) ? 'P' : '.',
191 (v
& CR_TLBMISC_D
) ? 'D' : '.',
192 (v
& CR_TLBMISC_PID_MASK
) >> 4));
194 if ((v
& CR_TLBMISC_PID_MASK
) !=
195 (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
)) {
196 mmu_flush_pid(env
, (env
->mmu
.tlbmisc_wr
& CR_TLBMISC_PID_MASK
) >>
197 CR_TLBMISC_PID_SHIFT
);
199 /* if tlbmisc.RD == 1 then trigger a TLB read on writes to TLBMISC */
200 if (v
& CR_TLBMISC_RD
) {
201 int way
= (v
>> CR_TLBMISC_WAY_SHIFT
);
202 int vpn
= (env
->mmu
.pteaddr_wr
& CR_PTEADDR_VPN_MASK
) >> 2;
203 Nios2TLBEntry
*entry
=
204 &env
->mmu
.tlb
[(way
* cpu
->tlb_num_ways
) +
205 (vpn
& env
->mmu
.tlb_entry_mask
)];
207 env
->regs
[CR_TLBACC
] &= CR_TLBACC_IGN_MASK
;
208 env
->regs
[CR_TLBACC
] |= entry
->data
;
209 env
->regs
[CR_TLBACC
] |= (entry
->tag
& (1 << 11)) ? CR_TLBACC_G
: 0;
210 env
->regs
[CR_TLBMISC
] =
211 (v
& ~CR_TLBMISC_PID_MASK
) |
212 ((entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1)) <<
213 CR_TLBMISC_PID_SHIFT
);
214 env
->regs
[CR_PTEADDR
] &= ~CR_PTEADDR_VPN_MASK
;
215 env
->regs
[CR_PTEADDR
] |= (entry
->tag
>> 12) << CR_PTEADDR_VPN_SHIFT
;
216 MMU_LOG(qemu_log("TLB READ way %d, vpn %05X, tag %08X, data %08X, "
217 "tlbacc %08X, tlbmisc %08X, pteaddr %08X\n",
218 way
, vpn
, entry
->tag
, entry
->data
,
219 env
->regs
[CR_TLBACC
], env
->regs
[CR_TLBMISC
],
220 env
->regs
[CR_PTEADDR
]));
222 env
->regs
[CR_TLBMISC
] = v
;
225 env
->mmu
.tlbmisc_wr
= v
;
229 MMU_LOG(qemu_log("PTEADDR: PTBASE %03X, VPN %05X\n",
230 v
>> CR_PTEADDR_PTBASE_SHIFT
,
231 (v
& CR_PTEADDR_VPN_MASK
) >> CR_PTEADDR_VPN_SHIFT
));
233 /* Writes to PTEADDR don't change the read-back VPN value */
234 env
->regs
[CR_PTEADDR
] = (v
& ~CR_PTEADDR_VPN_MASK
) |
235 (env
->regs
[CR_PTEADDR
] & CR_PTEADDR_VPN_MASK
);
236 env
->mmu
.pteaddr_wr
= v
;
244 void mmu_init(CPUNios2State
*env
)
246 Nios2CPU
*cpu
= env_archcpu(env
);
247 Nios2MMU
*mmu
= &env
->mmu
;
249 MMU_LOG(qemu_log("mmu_init\n"));
251 mmu
->tlb_entry_mask
= (cpu
->tlb_num_entries
/ cpu
->tlb_num_ways
) - 1;
252 mmu
->tlb
= g_new0(Nios2TLBEntry
, cpu
->tlb_num_entries
);
255 void dump_mmu(CPUNios2State
*env
)
257 Nios2CPU
*cpu
= env_archcpu(env
);
260 qemu_printf("MMU: ways %d, entries %d, pid bits %d\n",
261 cpu
->tlb_num_ways
, cpu
->tlb_num_entries
,
264 for (i
= 0; i
< cpu
->tlb_num_entries
; i
++) {
265 Nios2TLBEntry
*entry
= &env
->mmu
.tlb
[i
];
266 qemu_printf("TLB[%d] = %08X %08X %c VPN %05X "
267 "PID %02X %c PFN %05X %c%c%c%c\n",
268 i
, entry
->tag
, entry
->data
,
269 (entry
->tag
& (1 << 10)) ? 'V' : '-',
271 entry
->tag
& ((1 << cpu
->pid_num_bits
) - 1),
272 (entry
->tag
& (1 << 11)) ? 'G' : '-',
273 entry
->data
& CR_TLBACC_PFN_MASK
,
274 (entry
->data
& CR_TLBACC_C
) ? 'C' : '-',
275 (entry
->data
& CR_TLBACC_R
) ? 'R' : '-',
276 (entry
->data
& CR_TLBACC_W
) ? 'W' : '-',
277 (entry
->data
& CR_TLBACC_X
) ? 'X' : '-');
281 #endif /* !CONFIG_USER_ONLY */