Add qemu 2.4.0
[kvmfornfv.git] / qemu / target-microblaze / mmu.c
1 /*
2  *  Microblaze MMU emulation for qemu.
3  *
4  *  Copyright (c) 2009 Edgar E. Iglesias
5  *  Copyright (c) 2009-2012 PetaLogix Qld Pty Ltd.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #include "cpu.h"
22
23 #define D(x)
24
25 static unsigned int tlb_decode_size(unsigned int f)
26 {
27     static const unsigned int sizes[] = {
28         1 * 1024, 4 * 1024, 16 * 1024, 64 * 1024, 256 * 1024,
29         1 * 1024 * 1024, 4 * 1024 * 1024, 16 * 1024 * 1024
30     };
31     assert(f < ARRAY_SIZE(sizes));
32     return sizes[f];
33 }
34
35 static void mmu_flush_idx(CPUMBState *env, unsigned int idx)
36 {
37     CPUState *cs = CPU(mb_env_get_cpu(env));
38     struct microblaze_mmu *mmu = &env->mmu;
39     unsigned int tlb_size;
40     uint32_t tlb_tag, end, t;
41
42     t = mmu->rams[RAM_TAG][idx];
43     if (!(t & TLB_VALID))
44         return;
45
46     tlb_tag = t & TLB_EPN_MASK;
47     tlb_size = tlb_decode_size((t & TLB_PAGESZ_MASK) >> 7);
48     end = tlb_tag + tlb_size;
49
50     while (tlb_tag < end) {
51         tlb_flush_page(cs, tlb_tag);
52         tlb_tag += TARGET_PAGE_SIZE;
53     }
54 }
55
56 static void mmu_change_pid(CPUMBState *env, unsigned int newpid) 
57 {
58     struct microblaze_mmu *mmu = &env->mmu;
59     unsigned int i;
60     uint32_t t;
61
62     if (newpid & ~0xff)
63         qemu_log("Illegal rpid=%x\n", newpid);
64
65     for (i = 0; i < ARRAY_SIZE(mmu->rams[RAM_TAG]); i++) {
66         /* Lookup and decode.  */
67         t = mmu->rams[RAM_TAG][i];
68         if (t & TLB_VALID) {
69             if (mmu->tids[i] && ((mmu->regs[MMU_R_PID] & 0xff) == mmu->tids[i]))
70                 mmu_flush_idx(env, i);
71         }
72     }
73 }
74
75 /* rw - 0 = read, 1 = write, 2 = fetch.  */
76 unsigned int mmu_translate(struct microblaze_mmu *mmu,
77                            struct microblaze_mmu_lookup *lu,
78                            target_ulong vaddr, int rw, int mmu_idx)
79 {
80     unsigned int i, hit = 0;
81     unsigned int tlb_ex = 0, tlb_wr = 0, tlb_zsel;
82     unsigned int tlb_size;
83     uint32_t tlb_tag, tlb_rpn, mask, t0;
84
85     lu->err = ERR_MISS;
86     for (i = 0; i < ARRAY_SIZE(mmu->rams[RAM_TAG]); i++) {
87         uint32_t t, d;
88
89         /* Lookup and decode.  */
90         t = mmu->rams[RAM_TAG][i];
91         D(qemu_log("TLB %d valid=%d\n", i, t & TLB_VALID));
92         if (t & TLB_VALID) {
93             tlb_size = tlb_decode_size((t & TLB_PAGESZ_MASK) >> 7);
94             if (tlb_size < TARGET_PAGE_SIZE) {
95                 qemu_log("%d pages not supported\n", tlb_size);
96                 abort();
97             }
98
99             mask = ~(tlb_size - 1);
100             tlb_tag = t & TLB_EPN_MASK;
101             if ((vaddr & mask) != (tlb_tag & mask)) {
102                 D(qemu_log("TLB %d vaddr=%x != tag=%x\n",
103                            i, vaddr & mask, tlb_tag & mask));
104                 continue;
105             }
106             if (mmu->tids[i]
107                 && ((mmu->regs[MMU_R_PID] & 0xff) != mmu->tids[i])) {
108                 D(qemu_log("TLB %d pid=%x != tid=%x\n",
109                            i, mmu->regs[MMU_R_PID], mmu->tids[i]));
110                 continue;
111             }
112
113             /* Bring in the data part.  */
114             d = mmu->rams[RAM_DATA][i];
115             tlb_ex = d & TLB_EX;
116             tlb_wr = d & TLB_WR;
117
118             /* Now let's see if there is a zone that overrides the protbits.  */
119             tlb_zsel = (d >> 4) & 0xf;
120             t0 = mmu->regs[MMU_R_ZPR] >> (30 - (tlb_zsel * 2));
121             t0 &= 0x3;
122
123             if (tlb_zsel > mmu->c_mmu_zones) {
124                 qemu_log("tlb zone select out of range! %d\n", tlb_zsel);
125                 t0 = 1; /* Ignore.  */
126             }
127
128             if (mmu->c_mmu == 1) {
129                 t0 = 1; /* Zones are disabled.  */
130             }
131
132             switch (t0) {
133                 case 0:
134                     if (mmu_idx == MMU_USER_IDX)
135                         continue;
136                     break;
137                 case 2:
138                     if (mmu_idx != MMU_USER_IDX) {
139                         tlb_ex = 1;
140                         tlb_wr = 1;
141                     }
142                     break;
143                 case 3:
144                     tlb_ex = 1;
145                     tlb_wr = 1;
146                     break;
147                 default: break;
148             }
149
150             lu->err = ERR_PROT;
151             lu->prot = PAGE_READ;
152             if (tlb_wr)
153                 lu->prot |= PAGE_WRITE;
154             else if (rw == 1)
155                 goto done;
156             if (tlb_ex)
157                 lu->prot |=PAGE_EXEC;
158             else if (rw == 2) {
159                 goto done;
160             }
161
162             tlb_rpn = d & TLB_RPN_MASK;
163
164             lu->vaddr = tlb_tag;
165             lu->paddr = tlb_rpn;
166             lu->size = tlb_size;
167             lu->err = ERR_HIT;
168             lu->idx = i;
169             hit = 1;
170             goto done;
171         }
172     }
173 done:
174     D(qemu_log("MMU vaddr=%x rw=%d tlb_wr=%d tlb_ex=%d hit=%d\n",
175               vaddr, rw, tlb_wr, tlb_ex, hit));
176     return hit;
177 }
178
179 /* Writes/reads to the MMU's special regs end up here.  */
180 uint32_t mmu_read(CPUMBState *env, uint32_t rn)
181 {
182     unsigned int i;
183     uint32_t r;
184
185     if (env->mmu.c_mmu < 2 || !env->mmu.c_mmu_tlb_access) {
186         qemu_log("MMU access on MMU-less system\n");
187         return 0;
188     }
189
190     switch (rn) {
191         /* Reads to HI/LO trig reads from the mmu rams.  */
192         case MMU_R_TLBLO:
193         case MMU_R_TLBHI:
194             if (!(env->mmu.c_mmu_tlb_access & 1)) {
195                 qemu_log("Invalid access to MMU reg %d\n", rn);
196                 return 0;
197             }
198
199             i = env->mmu.regs[MMU_R_TLBX] & 0xff;
200             r = env->mmu.rams[rn & 1][i];
201             if (rn == MMU_R_TLBHI)
202                 env->mmu.regs[MMU_R_PID] = env->mmu.tids[i];
203             break;
204         case MMU_R_PID:
205         case MMU_R_ZPR:
206             if (!(env->mmu.c_mmu_tlb_access & 1)) {
207                 qemu_log("Invalid access to MMU reg %d\n", rn);
208                 return 0;
209             }
210             r = env->mmu.regs[rn];
211             break;
212         default:
213             r = env->mmu.regs[rn];
214             break;
215     }
216     D(qemu_log("%s rn=%d=%x\n", __func__, rn, r));
217     return r;
218 }
219
220 void mmu_write(CPUMBState *env, uint32_t rn, uint32_t v)
221 {
222     MicroBlazeCPU *cpu = mb_env_get_cpu(env);
223     unsigned int i;
224     D(qemu_log("%s rn=%d=%x old=%x\n", __func__, rn, v, env->mmu.regs[rn]));
225
226     if (env->mmu.c_mmu < 2 || !env->mmu.c_mmu_tlb_access) {
227         qemu_log("MMU access on MMU-less system\n");
228         return;
229     }
230
231     switch (rn) {
232         /* Writes to HI/LO trig writes to the mmu rams.  */
233         case MMU_R_TLBLO:
234         case MMU_R_TLBHI:
235             i = env->mmu.regs[MMU_R_TLBX] & 0xff;
236             if (rn == MMU_R_TLBHI) {
237                 if (i < 3 && !(v & TLB_VALID) && qemu_loglevel_mask(~0))
238                     qemu_log("invalidating index %x at pc=%x\n",
239                              i, env->sregs[SR_PC]);
240                 env->mmu.tids[i] = env->mmu.regs[MMU_R_PID] & 0xff;
241                 mmu_flush_idx(env, i);
242             }
243             env->mmu.rams[rn & 1][i] = v;
244
245             D(qemu_log("%s ram[%d][%d]=%x\n", __func__, rn & 1, i, v));
246             break;
247         case MMU_R_ZPR:
248             if (env->mmu.c_mmu_tlb_access <= 1) {
249                 qemu_log("Invalid access to MMU reg %d\n", rn);
250                 return;
251             }
252
253             /* Changes to the zone protection reg flush the QEMU TLB.
254                Fortunately, these are very uncommon.  */
255             if (v != env->mmu.regs[rn]) {
256                 tlb_flush(CPU(cpu), 1);
257             }
258             env->mmu.regs[rn] = v;
259             break;
260         case MMU_R_PID:
261             if (env->mmu.c_mmu_tlb_access <= 1) {
262                 qemu_log("Invalid access to MMU reg %d\n", rn);
263                 return;
264             }
265
266             if (v != env->mmu.regs[rn]) {
267                 mmu_change_pid(env, v);
268                 env->mmu.regs[rn] = v;
269             }
270             break;
271         case MMU_R_TLBSX:
272         {
273             struct microblaze_mmu_lookup lu;
274             int hit;
275
276             if (env->mmu.c_mmu_tlb_access <= 1) {
277                 qemu_log("Invalid access to MMU reg %d\n", rn);
278                 return;
279             }
280
281             hit = mmu_translate(&env->mmu, &lu,
282                                 v & TLB_EPN_MASK, 0, cpu_mmu_index(env));
283             if (hit) {
284                 env->mmu.regs[MMU_R_TLBX] = lu.idx;
285             } else
286                 env->mmu.regs[MMU_R_TLBX] |= 0x80000000;
287             break;
288         }
289         default:
290             env->mmu.regs[rn] = v;
291             break;
292    }
293 }
294
295 void mmu_init(struct microblaze_mmu *mmu)
296 {
297     int i;
298     for (i = 0; i < ARRAY_SIZE(mmu->regs); i++) {
299         mmu->regs[i] = 0;
300     }
301 }