Commit | Line | Data |
---|---|---|
bbf45ba5 HB |
1 | /* |
2 | * This program is free software; you can redistribute it and/or modify | |
3 | * it under the terms of the GNU General Public License, version 2, as | |
4 | * published by the Free Software Foundation. | |
5 | * | |
6 | * This program is distributed in the hope that it will be useful, | |
7 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
8 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
9 | * GNU General Public License for more details. | |
10 | * | |
11 | * You should have received a copy of the GNU General Public License | |
12 | * along with this program; if not, write to the Free Software | |
13 | * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
14 | * | |
15 | * Copyright IBM Corp. 2007 | |
16 | * | |
17 | * Authors: Hollis Blanchard <hollisb@us.ibm.com> | |
18 | */ | |
19 | ||
20 | #include <linux/types.h> | |
21 | #include <linux/string.h> | |
31711f22 | 22 | #include <linux/kvm.h> |
bbf45ba5 HB |
23 | #include <linux/kvm_host.h> |
24 | #include <linux/highmem.h> | |
25 | #include <asm/mmu-44x.h> | |
26 | #include <asm/kvm_ppc.h> | |
db93f574 | 27 | #include <asm/kvm_44x.h> |
bbf45ba5 HB |
28 | |
29 | #include "44x_tlb.h" | |
30 | ||
31 | #define PPC44x_TLB_USER_PERM_MASK (PPC44x_TLB_UX|PPC44x_TLB_UR|PPC44x_TLB_UW) | |
32 | #define PPC44x_TLB_SUPER_PERM_MASK (PPC44x_TLB_SX|PPC44x_TLB_SR|PPC44x_TLB_SW) | |
33 | ||
34 | static unsigned int kvmppc_tlb_44x_pos; | |
35 | ||
a0d7b9f2 HB |
36 | #ifdef DEBUG |
37 | void kvmppc_dump_tlbs(struct kvm_vcpu *vcpu) | |
38 | { | |
39 | struct kvmppc_44x_tlbe *tlbe; | |
40 | int i; | |
41 | ||
42 | printk("vcpu %d TLB dump:\n", vcpu->vcpu_id); | |
43 | printk("| %2s | %3s | %8s | %8s | %8s |\n", | |
44 | "nr", "tid", "word0", "word1", "word2"); | |
45 | ||
46 | for (i = 0; i < PPC44x_TLB_SIZE; i++) { | |
db93f574 | 47 | tlbe = &vcpu_44x->guest_tlb[i]; |
a0d7b9f2 HB |
48 | if (tlbe->word0 & PPC44x_TLB_VALID) |
49 | printk(" G%2d | %02X | %08X | %08X | %08X |\n", | |
50 | i, tlbe->tid, tlbe->word0, tlbe->word1, | |
51 | tlbe->word2); | |
52 | } | |
53 | ||
54 | for (i = 0; i < PPC44x_TLB_SIZE; i++) { | |
db93f574 | 55 | tlbe = &vcpu_44x->shadow_tlb[i]; |
a0d7b9f2 HB |
56 | if (tlbe->word0 & PPC44x_TLB_VALID) |
57 | printk(" S%2d | %02X | %08X | %08X | %08X |\n", | |
58 | i, tlbe->tid, tlbe->word0, tlbe->word1, | |
59 | tlbe->word2); | |
60 | } | |
61 | } | |
62 | #endif | |
63 | ||
bbf45ba5 HB |
64 | static u32 kvmppc_44x_tlb_shadow_attrib(u32 attrib, int usermode) |
65 | { | |
66 | /* Mask off reserved bits. */ | |
67 | attrib &= PPC44x_TLB_PERM_MASK|PPC44x_TLB_ATTR_MASK; | |
68 | ||
69 | if (!usermode) { | |
70 | /* Guest is in supervisor mode, so we need to translate guest | |
71 | * supervisor permissions into user permissions. */ | |
72 | attrib &= ~PPC44x_TLB_USER_PERM_MASK; | |
73 | attrib |= (attrib & PPC44x_TLB_SUPER_PERM_MASK) << 3; | |
74 | } | |
75 | ||
76 | /* Make sure host can always access this memory. */ | |
77 | attrib |= PPC44x_TLB_SX|PPC44x_TLB_SR|PPC44x_TLB_SW; | |
78 | ||
79 | return attrib; | |
80 | } | |
81 | ||
82 | /* Search the guest TLB for a matching entry. */ | |
83 | int kvmppc_44x_tlb_index(struct kvm_vcpu *vcpu, gva_t eaddr, unsigned int pid, | |
84 | unsigned int as) | |
85 | { | |
db93f574 | 86 | struct kvmppc_vcpu_44x *vcpu_44x = to_44x(vcpu); |
bbf45ba5 HB |
87 | int i; |
88 | ||
89 | /* XXX Replace loop with fancy data structures. */ | |
90 | for (i = 0; i < PPC44x_TLB_SIZE; i++) { | |
db93f574 | 91 | struct kvmppc_44x_tlbe *tlbe = &vcpu_44x->guest_tlb[i]; |
bbf45ba5 HB |
92 | unsigned int tid; |
93 | ||
94 | if (eaddr < get_tlb_eaddr(tlbe)) | |
95 | continue; | |
96 | ||
97 | if (eaddr > get_tlb_end(tlbe)) | |
98 | continue; | |
99 | ||
100 | tid = get_tlb_tid(tlbe); | |
101 | if (tid && (tid != pid)) | |
102 | continue; | |
103 | ||
104 | if (!get_tlb_v(tlbe)) | |
105 | continue; | |
106 | ||
107 | if (get_tlb_ts(tlbe) != as) | |
108 | continue; | |
109 | ||
110 | return i; | |
111 | } | |
112 | ||
113 | return -1; | |
114 | } | |
115 | ||
0f55dc48 HB |
116 | struct kvmppc_44x_tlbe *kvmppc_44x_itlb_search(struct kvm_vcpu *vcpu, |
117 | gva_t eaddr) | |
bbf45ba5 | 118 | { |
db93f574 | 119 | struct kvmppc_vcpu_44x *vcpu_44x = to_44x(vcpu); |
bbf45ba5 HB |
120 | unsigned int as = !!(vcpu->arch.msr & MSR_IS); |
121 | unsigned int index; | |
122 | ||
123 | index = kvmppc_44x_tlb_index(vcpu, eaddr, vcpu->arch.pid, as); | |
124 | if (index == -1) | |
125 | return NULL; | |
db93f574 | 126 | return &vcpu_44x->guest_tlb[index]; |
bbf45ba5 HB |
127 | } |
128 | ||
0f55dc48 HB |
129 | struct kvmppc_44x_tlbe *kvmppc_44x_dtlb_search(struct kvm_vcpu *vcpu, |
130 | gva_t eaddr) | |
bbf45ba5 | 131 | { |
db93f574 | 132 | struct kvmppc_vcpu_44x *vcpu_44x = to_44x(vcpu); |
bbf45ba5 HB |
133 | unsigned int as = !!(vcpu->arch.msr & MSR_DS); |
134 | unsigned int index; | |
135 | ||
136 | index = kvmppc_44x_tlb_index(vcpu, eaddr, vcpu->arch.pid, as); | |
137 | if (index == -1) | |
138 | return NULL; | |
db93f574 | 139 | return &vcpu_44x->guest_tlb[index]; |
bbf45ba5 HB |
140 | } |
141 | ||
0f55dc48 | 142 | static int kvmppc_44x_tlbe_is_writable(struct kvmppc_44x_tlbe *tlbe) |
bbf45ba5 HB |
143 | { |
144 | return tlbe->word2 & (PPC44x_TLB_SW|PPC44x_TLB_UW); | |
145 | } | |
146 | ||
bbf45ba5 HB |
147 | static void kvmppc_44x_shadow_release(struct kvm_vcpu *vcpu, |
148 | unsigned int index) | |
149 | { | |
db93f574 HB |
150 | struct kvmppc_vcpu_44x *vcpu_44x = to_44x(vcpu); |
151 | struct kvmppc_44x_tlbe *stlbe = &vcpu_44x->shadow_tlb[index]; | |
152 | struct page *page = vcpu_44x->shadow_pages[index]; | |
bbf45ba5 | 153 | |
bbf45ba5 HB |
154 | if (get_tlb_v(stlbe)) { |
155 | if (kvmppc_44x_tlbe_is_writable(stlbe)) | |
156 | kvm_release_page_dirty(page); | |
157 | else | |
158 | kvm_release_page_clean(page); | |
159 | } | |
160 | } | |
161 | ||
c30f8a6c HB |
162 | void kvmppc_core_destroy_mmu(struct kvm_vcpu *vcpu) |
163 | { | |
164 | int i; | |
165 | ||
166 | for (i = 0; i <= tlb_44x_hwater; i++) | |
167 | kvmppc_44x_shadow_release(vcpu, i); | |
168 | } | |
169 | ||
83aae4a8 HB |
170 | void kvmppc_tlbe_set_modified(struct kvm_vcpu *vcpu, unsigned int i) |
171 | { | |
db93f574 HB |
172 | struct kvmppc_vcpu_44x *vcpu_44x = to_44x(vcpu); |
173 | ||
174 | vcpu_44x->shadow_tlb_mod[i] = 1; | |
83aae4a8 HB |
175 | } |
176 | ||
bbf45ba5 HB |
177 | /* Caller must ensure that the specified guest TLB entry is safe to insert into |
178 | * the shadow TLB. */ | |
179 | void kvmppc_mmu_map(struct kvm_vcpu *vcpu, u64 gvaddr, gfn_t gfn, u64 asid, | |
180 | u32 flags) | |
181 | { | |
db93f574 | 182 | struct kvmppc_vcpu_44x *vcpu_44x = to_44x(vcpu); |
bbf45ba5 | 183 | struct page *new_page; |
0f55dc48 | 184 | struct kvmppc_44x_tlbe *stlbe; |
bbf45ba5 HB |
185 | hpa_t hpaddr; |
186 | unsigned int victim; | |
187 | ||
188 | /* Future optimization: don't overwrite the TLB entry containing the | |
189 | * current PC (or stack?). */ | |
190 | victim = kvmppc_tlb_44x_pos++; | |
191 | if (kvmppc_tlb_44x_pos > tlb_44x_hwater) | |
192 | kvmppc_tlb_44x_pos = 0; | |
db93f574 | 193 | stlbe = &vcpu_44x->shadow_tlb[victim]; |
bbf45ba5 HB |
194 | |
195 | /* Get reference to new page. */ | |
bbf45ba5 HB |
196 | new_page = gfn_to_page(vcpu->kvm, gfn); |
197 | if (is_error_page(new_page)) { | |
9dcb40e1 | 198 | printk(KERN_ERR "Couldn't get guest page for gfn %lx!\n", gfn); |
bbf45ba5 HB |
199 | kvm_release_page_clean(new_page); |
200 | return; | |
201 | } | |
202 | hpaddr = page_to_phys(new_page); | |
203 | ||
204 | /* Drop reference to old page. */ | |
205 | kvmppc_44x_shadow_release(vcpu, victim); | |
bbf45ba5 | 206 | |
db93f574 | 207 | vcpu_44x->shadow_pages[victim] = new_page; |
bbf45ba5 HB |
208 | |
209 | /* XXX Make sure (va, size) doesn't overlap any other | |
210 | * entries. 440x6 user manual says the result would be | |
211 | * "undefined." */ | |
212 | ||
213 | /* XXX what about AS? */ | |
214 | ||
49dd2c49 | 215 | stlbe->tid = !(asid & 0xff); |
bbf45ba5 HB |
216 | |
217 | /* Force TS=1 for all guest mappings. */ | |
218 | /* For now we hardcode 4KB mappings, but it will be important to | |
219 | * use host large pages in the future. */ | |
220 | stlbe->word0 = (gvaddr & PAGE_MASK) | PPC44x_TLB_VALID | PPC44x_TLB_TS | |
221 | | PPC44x_TLB_4K; | |
bbf45ba5 HB |
222 | stlbe->word1 = (hpaddr & 0xfffffc00) | ((hpaddr >> 32) & 0xf); |
223 | stlbe->word2 = kvmppc_44x_tlb_shadow_attrib(flags, | |
224 | vcpu->arch.msr & MSR_PR); | |
83aae4a8 | 225 | kvmppc_tlbe_set_modified(vcpu, victim); |
31711f22 JY |
226 | |
227 | KVMTRACE_5D(STLB_WRITE, vcpu, victim, | |
228 | stlbe->tid, stlbe->word0, stlbe->word1, stlbe->word2, | |
229 | handler); | |
bbf45ba5 HB |
230 | } |
231 | ||
a0d7b9f2 HB |
232 | static void kvmppc_mmu_invalidate(struct kvm_vcpu *vcpu, gva_t eaddr, |
233 | gva_t eend, u32 asid) | |
bbf45ba5 | 234 | { |
db93f574 | 235 | struct kvmppc_vcpu_44x *vcpu_44x = to_44x(vcpu); |
49dd2c49 | 236 | unsigned int pid = !(asid & 0xff); |
bbf45ba5 HB |
237 | int i; |
238 | ||
239 | /* XXX Replace loop with fancy data structures. */ | |
bbf45ba5 | 240 | for (i = 0; i <= tlb_44x_hwater; i++) { |
db93f574 | 241 | struct kvmppc_44x_tlbe *stlbe = &vcpu_44x->shadow_tlb[i]; |
bbf45ba5 HB |
242 | unsigned int tid; |
243 | ||
244 | if (!get_tlb_v(stlbe)) | |
245 | continue; | |
246 | ||
cc04454f | 247 | if (eend < get_tlb_eaddr(stlbe)) |
bbf45ba5 HB |
248 | continue; |
249 | ||
250 | if (eaddr > get_tlb_end(stlbe)) | |
251 | continue; | |
252 | ||
253 | tid = get_tlb_tid(stlbe); | |
254 | if (tid && (tid != pid)) | |
255 | continue; | |
256 | ||
257 | kvmppc_44x_shadow_release(vcpu, i); | |
258 | stlbe->word0 = 0; | |
83aae4a8 | 259 | kvmppc_tlbe_set_modified(vcpu, i); |
31711f22 JY |
260 | KVMTRACE_5D(STLB_INVAL, vcpu, i, |
261 | stlbe->tid, stlbe->word0, stlbe->word1, | |
262 | stlbe->word2, handler); | |
bbf45ba5 | 263 | } |
bbf45ba5 HB |
264 | } |
265 | ||
49dd2c49 HB |
266 | /* Invalidate all mappings on the privilege switch after PID has been changed. |
267 | * The guest always runs with PID=1, so we must clear the entire TLB when | |
268 | * switching address spaces. */ | |
bbf45ba5 HB |
269 | void kvmppc_mmu_priv_switch(struct kvm_vcpu *vcpu, int usermode) |
270 | { | |
db93f574 | 271 | struct kvmppc_vcpu_44x *vcpu_44x = to_44x(vcpu); |
bbf45ba5 HB |
272 | int i; |
273 | ||
49dd2c49 HB |
274 | if (vcpu->arch.swap_pid) { |
275 | /* XXX Replace loop with fancy data structures. */ | |
49dd2c49 | 276 | for (i = 0; i <= tlb_44x_hwater; i++) { |
db93f574 | 277 | struct kvmppc_44x_tlbe *stlbe = &vcpu_44x->shadow_tlb[i]; |
49dd2c49 HB |
278 | |
279 | /* Future optimization: clear only userspace mappings. */ | |
280 | kvmppc_44x_shadow_release(vcpu, i); | |
281 | stlbe->word0 = 0; | |
282 | kvmppc_tlbe_set_modified(vcpu, i); | |
283 | KVMTRACE_5D(STLB_INVAL, vcpu, i, | |
284 | stlbe->tid, stlbe->word0, stlbe->word1, | |
285 | stlbe->word2, handler); | |
286 | } | |
49dd2c49 | 287 | vcpu->arch.swap_pid = 0; |
bbf45ba5 | 288 | } |
49dd2c49 HB |
289 | |
290 | vcpu->arch.shadow_pid = !usermode; | |
bbf45ba5 | 291 | } |
a0d7b9f2 HB |
292 | |
293 | static int tlbe_is_host_safe(const struct kvm_vcpu *vcpu, | |
0f55dc48 | 294 | const struct kvmppc_44x_tlbe *tlbe) |
a0d7b9f2 HB |
295 | { |
296 | gpa_t gpa; | |
297 | ||
298 | if (!get_tlb_v(tlbe)) | |
299 | return 0; | |
300 | ||
301 | /* Does it match current guest AS? */ | |
302 | /* XXX what about IS != DS? */ | |
303 | if (get_tlb_ts(tlbe) != !!(vcpu->arch.msr & MSR_IS)) | |
304 | return 0; | |
305 | ||
306 | gpa = get_tlb_raddr(tlbe); | |
307 | if (!gfn_to_memslot(vcpu->kvm, gpa >> PAGE_SHIFT)) | |
308 | /* Mapping is not for RAM. */ | |
309 | return 0; | |
310 | ||
311 | return 1; | |
312 | } | |
313 | ||
75f74f0d | 314 | int kvmppc_44x_emul_tlbwe(struct kvm_vcpu *vcpu, u8 ra, u8 rs, u8 ws) |
a0d7b9f2 | 315 | { |
db93f574 | 316 | struct kvmppc_vcpu_44x *vcpu_44x = to_44x(vcpu); |
a0d7b9f2 HB |
317 | u64 eaddr; |
318 | u64 raddr; | |
319 | u64 asid; | |
320 | u32 flags; | |
0f55dc48 | 321 | struct kvmppc_44x_tlbe *tlbe; |
a0d7b9f2 HB |
322 | unsigned int index; |
323 | ||
324 | index = vcpu->arch.gpr[ra]; | |
325 | if (index > PPC44x_TLB_SIZE) { | |
326 | printk("%s: index %d\n", __func__, index); | |
327 | kvmppc_dump_vcpu(vcpu); | |
328 | return EMULATE_FAIL; | |
329 | } | |
330 | ||
db93f574 | 331 | tlbe = &vcpu_44x->guest_tlb[index]; |
a0d7b9f2 HB |
332 | |
333 | /* Invalidate shadow mappings for the about-to-be-clobbered TLBE. */ | |
334 | if (tlbe->word0 & PPC44x_TLB_VALID) { | |
335 | eaddr = get_tlb_eaddr(tlbe); | |
336 | asid = (tlbe->word0 & PPC44x_TLB_TS) | tlbe->tid; | |
337 | kvmppc_mmu_invalidate(vcpu, eaddr, get_tlb_end(tlbe), asid); | |
338 | } | |
339 | ||
340 | switch (ws) { | |
341 | case PPC44x_TLB_PAGEID: | |
bf5d4025 | 342 | tlbe->tid = get_mmucr_stid(vcpu); |
a0d7b9f2 HB |
343 | tlbe->word0 = vcpu->arch.gpr[rs]; |
344 | break; | |
345 | ||
346 | case PPC44x_TLB_XLAT: | |
347 | tlbe->word1 = vcpu->arch.gpr[rs]; | |
348 | break; | |
349 | ||
350 | case PPC44x_TLB_ATTRIB: | |
351 | tlbe->word2 = vcpu->arch.gpr[rs]; | |
352 | break; | |
353 | ||
354 | default: | |
355 | return EMULATE_FAIL; | |
356 | } | |
357 | ||
358 | if (tlbe_is_host_safe(vcpu, tlbe)) { | |
359 | eaddr = get_tlb_eaddr(tlbe); | |
360 | raddr = get_tlb_raddr(tlbe); | |
361 | asid = (tlbe->word0 & PPC44x_TLB_TS) | tlbe->tid; | |
362 | flags = tlbe->word2 & 0xffff; | |
363 | ||
364 | /* Create a 4KB mapping on the host. If the guest wanted a | |
365 | * large page, only the first 4KB is mapped here and the rest | |
366 | * are mapped on the fly. */ | |
367 | kvmppc_mmu_map(vcpu, eaddr, raddr >> PAGE_SHIFT, asid, flags); | |
368 | } | |
369 | ||
370 | KVMTRACE_5D(GTLB_WRITE, vcpu, index, | |
371 | tlbe->tid, tlbe->word0, tlbe->word1, tlbe->word2, | |
372 | handler); | |
373 | ||
374 | return EMULATE_DONE; | |
375 | } | |
376 | ||
75f74f0d | 377 | int kvmppc_44x_emul_tlbsx(struct kvm_vcpu *vcpu, u8 rt, u8 ra, u8 rb, u8 rc) |
a0d7b9f2 HB |
378 | { |
379 | u32 ea; | |
380 | int index; | |
381 | unsigned int as = get_mmucr_sts(vcpu); | |
382 | unsigned int pid = get_mmucr_stid(vcpu); | |
383 | ||
384 | ea = vcpu->arch.gpr[rb]; | |
385 | if (ra) | |
386 | ea += vcpu->arch.gpr[ra]; | |
387 | ||
388 | index = kvmppc_44x_tlb_index(vcpu, ea, pid, as); | |
389 | if (rc) { | |
390 | if (index < 0) | |
391 | vcpu->arch.cr &= ~0x20000000; | |
392 | else | |
393 | vcpu->arch.cr |= 0x20000000; | |
394 | } | |
395 | vcpu->arch.gpr[rt] = index; | |
396 | ||
397 | return EMULATE_DONE; | |
398 | } |