Sanjay Lal | f5c236d | 2012-11-21 18:34:09 -0800 | [diff] [blame] | 1 | /* |
| 2 | * This file is subject to the terms and conditions of the GNU General Public |
| 3 | * License. See the file "COPYING" in the main directory of this archive |
| 4 | * for more details. |
| 5 | * |
| 6 | * KVM/MIPS: Deliver/Emulate exceptions to the guest kernel |
| 7 | * |
| 8 | * Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved. |
| 9 | * Authors: Sanjay Lal <sanjayl@kymasys.com> |
| 10 | */ |
| 11 | |
| 12 | #include <linux/errno.h> |
| 13 | #include <linux/err.h> |
| 14 | #include <linux/module.h> |
| 15 | #include <linux/vmalloc.h> |
| 16 | |
| 17 | #include <linux/kvm_host.h> |
| 18 | |
| 19 | #include "kvm_mips_opcode.h" |
| 20 | #include "kvm_mips_int.h" |
| 21 | |
| 22 | static gpa_t kvm_trap_emul_gva_to_gpa_cb(gva_t gva) |
| 23 | { |
| 24 | gpa_t gpa; |
| 25 | uint32_t kseg = KSEGX(gva); |
| 26 | |
| 27 | if ((kseg == CKSEG0) || (kseg == CKSEG1)) |
| 28 | gpa = CPHYSADDR(gva); |
| 29 | else { |
| 30 | printk("%s: cannot find GPA for GVA: %#lx\n", __func__, gva); |
| 31 | kvm_mips_dump_host_tlbs(); |
| 32 | gpa = KVM_INVALID_ADDR; |
| 33 | } |
| 34 | |
| 35 | #ifdef DEBUG |
| 36 | kvm_debug("%s: gva %#lx, gpa: %#llx\n", __func__, gva, gpa); |
| 37 | #endif |
| 38 | |
| 39 | return gpa; |
| 40 | } |
| 41 | |
| 42 | |
| 43 | static int kvm_trap_emul_handle_cop_unusable(struct kvm_vcpu *vcpu) |
| 44 | { |
| 45 | struct kvm_run *run = vcpu->run; |
| 46 | uint32_t __user *opc = (uint32_t __user *) vcpu->arch.pc; |
| 47 | unsigned long cause = vcpu->arch.host_cp0_cause; |
| 48 | enum emulation_result er = EMULATE_DONE; |
| 49 | int ret = RESUME_GUEST; |
| 50 | |
| 51 | if (((cause & CAUSEF_CE) >> CAUSEB_CE) == 1) { |
| 52 | er = kvm_mips_emulate_fpu_exc(cause, opc, run, vcpu); |
| 53 | } else |
| 54 | er = kvm_mips_emulate_inst(cause, opc, run, vcpu); |
| 55 | |
| 56 | switch (er) { |
| 57 | case EMULATE_DONE: |
| 58 | ret = RESUME_GUEST; |
| 59 | break; |
| 60 | |
| 61 | case EMULATE_FAIL: |
| 62 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 63 | ret = RESUME_HOST; |
| 64 | break; |
| 65 | |
| 66 | case EMULATE_WAIT: |
| 67 | run->exit_reason = KVM_EXIT_INTR; |
| 68 | ret = RESUME_HOST; |
| 69 | break; |
| 70 | |
| 71 | default: |
| 72 | BUG(); |
| 73 | } |
| 74 | return ret; |
| 75 | } |
| 76 | |
| 77 | static int kvm_trap_emul_handle_tlb_mod(struct kvm_vcpu *vcpu) |
| 78 | { |
| 79 | struct kvm_run *run = vcpu->run; |
| 80 | uint32_t __user *opc = (uint32_t __user *) vcpu->arch.pc; |
| 81 | unsigned long badvaddr = vcpu->arch.host_cp0_badvaddr; |
| 82 | unsigned long cause = vcpu->arch.host_cp0_cause; |
| 83 | enum emulation_result er = EMULATE_DONE; |
| 84 | int ret = RESUME_GUEST; |
| 85 | |
| 86 | if (KVM_GUEST_KSEGX(badvaddr) < KVM_GUEST_KSEG0 |
| 87 | || KVM_GUEST_KSEGX(badvaddr) == KVM_GUEST_KSEG23) { |
| 88 | #ifdef DEBUG |
| 89 | kvm_debug |
| 90 | ("USER/KSEG23 ADDR TLB MOD fault: cause %#lx, PC: %p, BadVaddr: %#lx\n", |
| 91 | cause, opc, badvaddr); |
| 92 | #endif |
| 93 | er = kvm_mips_handle_tlbmod(cause, opc, run, vcpu); |
| 94 | |
| 95 | if (er == EMULATE_DONE) |
| 96 | ret = RESUME_GUEST; |
| 97 | else { |
| 98 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 99 | ret = RESUME_HOST; |
| 100 | } |
| 101 | } else if (KVM_GUEST_KSEGX(badvaddr) == KVM_GUEST_KSEG0) { |
| 102 | /* XXXKYMA: The guest kernel does not expect to get this fault when we are not |
| 103 | * using HIGHMEM. Need to address this in a HIGHMEM kernel |
| 104 | */ |
| 105 | printk |
| 106 | ("TLB MOD fault not handled, cause %#lx, PC: %p, BadVaddr: %#lx\n", |
| 107 | cause, opc, badvaddr); |
| 108 | kvm_mips_dump_host_tlbs(); |
| 109 | kvm_arch_vcpu_dump_regs(vcpu); |
| 110 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 111 | ret = RESUME_HOST; |
| 112 | } else { |
| 113 | printk |
| 114 | ("Illegal TLB Mod fault address , cause %#lx, PC: %p, BadVaddr: %#lx\n", |
| 115 | cause, opc, badvaddr); |
| 116 | kvm_mips_dump_host_tlbs(); |
| 117 | kvm_arch_vcpu_dump_regs(vcpu); |
| 118 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 119 | ret = RESUME_HOST; |
| 120 | } |
| 121 | return ret; |
| 122 | } |
| 123 | |
| 124 | static int kvm_trap_emul_handle_tlb_st_miss(struct kvm_vcpu *vcpu) |
| 125 | { |
| 126 | struct kvm_run *run = vcpu->run; |
| 127 | uint32_t __user *opc = (uint32_t __user *) vcpu->arch.pc; |
| 128 | unsigned long badvaddr = vcpu->arch.host_cp0_badvaddr; |
| 129 | unsigned long cause = vcpu->arch.host_cp0_cause; |
| 130 | enum emulation_result er = EMULATE_DONE; |
| 131 | int ret = RESUME_GUEST; |
| 132 | |
| 133 | if (((badvaddr & PAGE_MASK) == KVM_GUEST_COMMPAGE_ADDR) |
| 134 | && KVM_GUEST_KERNEL_MODE(vcpu)) { |
| 135 | if (kvm_mips_handle_commpage_tlb_fault(badvaddr, vcpu) < 0) { |
| 136 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 137 | ret = RESUME_HOST; |
| 138 | } |
| 139 | } else if (KVM_GUEST_KSEGX(badvaddr) < KVM_GUEST_KSEG0 |
| 140 | || KVM_GUEST_KSEGX(badvaddr) == KVM_GUEST_KSEG23) { |
| 141 | #ifdef DEBUG |
| 142 | kvm_debug |
| 143 | ("USER ADDR TLB LD fault: cause %#lx, PC: %p, BadVaddr: %#lx\n", |
| 144 | cause, opc, badvaddr); |
| 145 | #endif |
| 146 | er = kvm_mips_handle_tlbmiss(cause, opc, run, vcpu); |
| 147 | if (er == EMULATE_DONE) |
| 148 | ret = RESUME_GUEST; |
| 149 | else { |
| 150 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 151 | ret = RESUME_HOST; |
| 152 | } |
| 153 | } else if (KVM_GUEST_KSEGX(badvaddr) == KVM_GUEST_KSEG0) { |
| 154 | /* All KSEG0 faults are handled by KVM, as the guest kernel does not |
| 155 | * expect to ever get them |
| 156 | */ |
| 157 | if (kvm_mips_handle_kseg0_tlb_fault |
| 158 | (vcpu->arch.host_cp0_badvaddr, vcpu) < 0) { |
| 159 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 160 | ret = RESUME_HOST; |
| 161 | } |
| 162 | } else { |
| 163 | kvm_err |
| 164 | ("Illegal TLB LD fault address , cause %#lx, PC: %p, BadVaddr: %#lx\n", |
| 165 | cause, opc, badvaddr); |
| 166 | kvm_mips_dump_host_tlbs(); |
| 167 | kvm_arch_vcpu_dump_regs(vcpu); |
| 168 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 169 | ret = RESUME_HOST; |
| 170 | } |
| 171 | return ret; |
| 172 | } |
| 173 | |
| 174 | static int kvm_trap_emul_handle_tlb_ld_miss(struct kvm_vcpu *vcpu) |
| 175 | { |
| 176 | struct kvm_run *run = vcpu->run; |
| 177 | uint32_t __user *opc = (uint32_t __user *) vcpu->arch.pc; |
| 178 | unsigned long badvaddr = vcpu->arch.host_cp0_badvaddr; |
| 179 | unsigned long cause = vcpu->arch.host_cp0_cause; |
| 180 | enum emulation_result er = EMULATE_DONE; |
| 181 | int ret = RESUME_GUEST; |
| 182 | |
| 183 | if (((badvaddr & PAGE_MASK) == KVM_GUEST_COMMPAGE_ADDR) |
| 184 | && KVM_GUEST_KERNEL_MODE(vcpu)) { |
| 185 | if (kvm_mips_handle_commpage_tlb_fault(badvaddr, vcpu) < 0) { |
| 186 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 187 | ret = RESUME_HOST; |
| 188 | } |
| 189 | } else if (KVM_GUEST_KSEGX(badvaddr) < KVM_GUEST_KSEG0 |
| 190 | || KVM_GUEST_KSEGX(badvaddr) == KVM_GUEST_KSEG23) { |
| 191 | #ifdef DEBUG |
| 192 | kvm_debug("USER ADDR TLB ST fault: PC: %#lx, BadVaddr: %#lx\n", |
| 193 | vcpu->arch.pc, badvaddr); |
| 194 | #endif |
| 195 | |
| 196 | /* User Address (UA) fault, this could happen if |
| 197 | * (1) TLB entry not present/valid in both Guest and shadow host TLBs, in this |
| 198 | * case we pass on the fault to the guest kernel and let it handle it. |
| 199 | * (2) TLB entry is present in the Guest TLB but not in the shadow, in this |
| 200 | * case we inject the TLB from the Guest TLB into the shadow host TLB |
| 201 | */ |
| 202 | |
| 203 | er = kvm_mips_handle_tlbmiss(cause, opc, run, vcpu); |
| 204 | if (er == EMULATE_DONE) |
| 205 | ret = RESUME_GUEST; |
| 206 | else { |
| 207 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 208 | ret = RESUME_HOST; |
| 209 | } |
| 210 | } else if (KVM_GUEST_KSEGX(badvaddr) == KVM_GUEST_KSEG0) { |
| 211 | if (kvm_mips_handle_kseg0_tlb_fault |
| 212 | (vcpu->arch.host_cp0_badvaddr, vcpu) < 0) { |
| 213 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 214 | ret = RESUME_HOST; |
| 215 | } |
| 216 | } else { |
| 217 | printk |
| 218 | ("Illegal TLB ST fault address , cause %#lx, PC: %p, BadVaddr: %#lx\n", |
| 219 | cause, opc, badvaddr); |
| 220 | kvm_mips_dump_host_tlbs(); |
| 221 | kvm_arch_vcpu_dump_regs(vcpu); |
| 222 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 223 | ret = RESUME_HOST; |
| 224 | } |
| 225 | return ret; |
| 226 | } |
| 227 | |
| 228 | static int kvm_trap_emul_handle_addr_err_st(struct kvm_vcpu *vcpu) |
| 229 | { |
| 230 | struct kvm_run *run = vcpu->run; |
| 231 | uint32_t __user *opc = (uint32_t __user *) vcpu->arch.pc; |
| 232 | unsigned long badvaddr = vcpu->arch.host_cp0_badvaddr; |
| 233 | unsigned long cause = vcpu->arch.host_cp0_cause; |
| 234 | enum emulation_result er = EMULATE_DONE; |
| 235 | int ret = RESUME_GUEST; |
| 236 | |
| 237 | if (KVM_GUEST_KERNEL_MODE(vcpu) |
| 238 | && (KSEGX(badvaddr) == CKSEG0 || KSEGX(badvaddr) == CKSEG1)) { |
| 239 | #ifdef DEBUG |
| 240 | kvm_debug("Emulate Store to MMIO space\n"); |
| 241 | #endif |
| 242 | er = kvm_mips_emulate_inst(cause, opc, run, vcpu); |
| 243 | if (er == EMULATE_FAIL) { |
| 244 | printk("Emulate Store to MMIO space failed\n"); |
| 245 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 246 | ret = RESUME_HOST; |
| 247 | } else { |
| 248 | run->exit_reason = KVM_EXIT_MMIO; |
| 249 | ret = RESUME_HOST; |
| 250 | } |
| 251 | } else { |
| 252 | printk |
| 253 | ("Address Error (STORE): cause %#lx, PC: %p, BadVaddr: %#lx\n", |
| 254 | cause, opc, badvaddr); |
| 255 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 256 | ret = RESUME_HOST; |
| 257 | } |
| 258 | return ret; |
| 259 | } |
| 260 | |
| 261 | static int kvm_trap_emul_handle_addr_err_ld(struct kvm_vcpu *vcpu) |
| 262 | { |
| 263 | struct kvm_run *run = vcpu->run; |
| 264 | uint32_t __user *opc = (uint32_t __user *) vcpu->arch.pc; |
| 265 | unsigned long badvaddr = vcpu->arch.host_cp0_badvaddr; |
| 266 | unsigned long cause = vcpu->arch.host_cp0_cause; |
| 267 | enum emulation_result er = EMULATE_DONE; |
| 268 | int ret = RESUME_GUEST; |
| 269 | |
| 270 | if (KSEGX(badvaddr) == CKSEG0 || KSEGX(badvaddr) == CKSEG1) { |
| 271 | #ifdef DEBUG |
| 272 | kvm_debug("Emulate Load from MMIO space @ %#lx\n", badvaddr); |
| 273 | #endif |
| 274 | er = kvm_mips_emulate_inst(cause, opc, run, vcpu); |
| 275 | if (er == EMULATE_FAIL) { |
| 276 | printk("Emulate Load from MMIO space failed\n"); |
| 277 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 278 | ret = RESUME_HOST; |
| 279 | } else { |
| 280 | run->exit_reason = KVM_EXIT_MMIO; |
| 281 | ret = RESUME_HOST; |
| 282 | } |
| 283 | } else { |
| 284 | printk |
| 285 | ("Address Error (LOAD): cause %#lx, PC: %p, BadVaddr: %#lx\n", |
| 286 | cause, opc, badvaddr); |
| 287 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 288 | ret = RESUME_HOST; |
| 289 | er = EMULATE_FAIL; |
| 290 | } |
| 291 | return ret; |
| 292 | } |
| 293 | |
| 294 | static int kvm_trap_emul_handle_syscall(struct kvm_vcpu *vcpu) |
| 295 | { |
| 296 | struct kvm_run *run = vcpu->run; |
| 297 | uint32_t __user *opc = (uint32_t __user *) vcpu->arch.pc; |
| 298 | unsigned long cause = vcpu->arch.host_cp0_cause; |
| 299 | enum emulation_result er = EMULATE_DONE; |
| 300 | int ret = RESUME_GUEST; |
| 301 | |
| 302 | er = kvm_mips_emulate_syscall(cause, opc, run, vcpu); |
| 303 | if (er == EMULATE_DONE) |
| 304 | ret = RESUME_GUEST; |
| 305 | else { |
| 306 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 307 | ret = RESUME_HOST; |
| 308 | } |
| 309 | return ret; |
| 310 | } |
| 311 | |
| 312 | static int kvm_trap_emul_handle_res_inst(struct kvm_vcpu *vcpu) |
| 313 | { |
| 314 | struct kvm_run *run = vcpu->run; |
| 315 | uint32_t __user *opc = (uint32_t __user *) vcpu->arch.pc; |
| 316 | unsigned long cause = vcpu->arch.host_cp0_cause; |
| 317 | enum emulation_result er = EMULATE_DONE; |
| 318 | int ret = RESUME_GUEST; |
| 319 | |
| 320 | er = kvm_mips_handle_ri(cause, opc, run, vcpu); |
| 321 | if (er == EMULATE_DONE) |
| 322 | ret = RESUME_GUEST; |
| 323 | else { |
| 324 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 325 | ret = RESUME_HOST; |
| 326 | } |
| 327 | return ret; |
| 328 | } |
| 329 | |
| 330 | static int kvm_trap_emul_handle_break(struct kvm_vcpu *vcpu) |
| 331 | { |
| 332 | struct kvm_run *run = vcpu->run; |
| 333 | uint32_t __user *opc = (uint32_t __user *) vcpu->arch.pc; |
| 334 | unsigned long cause = vcpu->arch.host_cp0_cause; |
| 335 | enum emulation_result er = EMULATE_DONE; |
| 336 | int ret = RESUME_GUEST; |
| 337 | |
| 338 | er = kvm_mips_emulate_bp_exc(cause, opc, run, vcpu); |
| 339 | if (er == EMULATE_DONE) |
| 340 | ret = RESUME_GUEST; |
| 341 | else { |
| 342 | run->exit_reason = KVM_EXIT_INTERNAL_ERROR; |
| 343 | ret = RESUME_HOST; |
| 344 | } |
| 345 | return ret; |
| 346 | } |
| 347 | |
Sanjay Lal | f5c236d | 2012-11-21 18:34:09 -0800 | [diff] [blame] | 348 | static int kvm_trap_emul_vm_init(struct kvm *kvm) |
| 349 | { |
| 350 | return 0; |
| 351 | } |
| 352 | |
| 353 | static int kvm_trap_emul_vcpu_init(struct kvm_vcpu *vcpu) |
| 354 | { |
| 355 | return 0; |
| 356 | } |
| 357 | |
| 358 | static int kvm_trap_emul_vcpu_setup(struct kvm_vcpu *vcpu) |
| 359 | { |
| 360 | struct mips_coproc *cop0 = vcpu->arch.cop0; |
| 361 | uint32_t config1; |
| 362 | int vcpu_id = vcpu->vcpu_id; |
| 363 | |
| 364 | /* Arch specific stuff, set up config registers properly so that the |
| 365 | * guest will come up as expected, for now we simulate a |
| 366 | * MIPS 24kc |
| 367 | */ |
| 368 | kvm_write_c0_guest_prid(cop0, 0x00019300); |
| 369 | kvm_write_c0_guest_config(cop0, |
| 370 | MIPS_CONFIG0 | (0x1 << CP0C0_AR) | |
| 371 | (MMU_TYPE_R4000 << CP0C0_MT)); |
| 372 | |
| 373 | /* Read the cache characteristics from the host Config1 Register */ |
| 374 | config1 = (read_c0_config1() & ~0x7f); |
| 375 | |
| 376 | /* Set up MMU size */ |
| 377 | config1 &= ~(0x3f << 25); |
| 378 | config1 |= ((KVM_MIPS_GUEST_TLB_SIZE - 1) << 25); |
| 379 | |
| 380 | /* We unset some bits that we aren't emulating */ |
| 381 | config1 &= |
| 382 | ~((1 << CP0C1_C2) | (1 << CP0C1_MD) | (1 << CP0C1_PC) | |
| 383 | (1 << CP0C1_WR) | (1 << CP0C1_CA)); |
| 384 | kvm_write_c0_guest_config1(cop0, config1); |
| 385 | |
| 386 | kvm_write_c0_guest_config2(cop0, MIPS_CONFIG2); |
| 387 | /* MIPS_CONFIG2 | (read_c0_config2() & 0xfff) */ |
| 388 | kvm_write_c0_guest_config3(cop0, |
| 389 | MIPS_CONFIG3 | (0 << CP0C3_VInt) | (1 << |
| 390 | CP0C3_ULRI)); |
| 391 | |
| 392 | /* Set Wait IE/IXMT Ignore in Config7, IAR, AR */ |
| 393 | kvm_write_c0_guest_config7(cop0, (MIPS_CONF7_WII) | (1 << 10)); |
| 394 | |
| 395 | /* Setup IntCtl defaults, compatibilty mode for timer interrupts (HW5) */ |
| 396 | kvm_write_c0_guest_intctl(cop0, 0xFC000000); |
| 397 | |
| 398 | /* Put in vcpu id as CPUNum into Ebase Reg to handle SMP Guests */ |
| 399 | kvm_write_c0_guest_ebase(cop0, KVM_GUEST_KSEG0 | (vcpu_id & 0xFF)); |
| 400 | |
| 401 | return 0; |
| 402 | } |
| 403 | |
| 404 | static struct kvm_mips_callbacks kvm_trap_emul_callbacks = { |
| 405 | /* exit handlers */ |
| 406 | .handle_cop_unusable = kvm_trap_emul_handle_cop_unusable, |
| 407 | .handle_tlb_mod = kvm_trap_emul_handle_tlb_mod, |
| 408 | .handle_tlb_st_miss = kvm_trap_emul_handle_tlb_st_miss, |
| 409 | .handle_tlb_ld_miss = kvm_trap_emul_handle_tlb_ld_miss, |
| 410 | .handle_addr_err_st = kvm_trap_emul_handle_addr_err_st, |
| 411 | .handle_addr_err_ld = kvm_trap_emul_handle_addr_err_ld, |
| 412 | .handle_syscall = kvm_trap_emul_handle_syscall, |
| 413 | .handle_res_inst = kvm_trap_emul_handle_res_inst, |
| 414 | .handle_break = kvm_trap_emul_handle_break, |
| 415 | |
| 416 | .vm_init = kvm_trap_emul_vm_init, |
| 417 | .vcpu_init = kvm_trap_emul_vcpu_init, |
| 418 | .vcpu_setup = kvm_trap_emul_vcpu_setup, |
| 419 | .gva_to_gpa = kvm_trap_emul_gva_to_gpa_cb, |
| 420 | .queue_timer_int = kvm_mips_queue_timer_int_cb, |
| 421 | .dequeue_timer_int = kvm_mips_dequeue_timer_int_cb, |
| 422 | .queue_io_int = kvm_mips_queue_io_int_cb, |
| 423 | .dequeue_io_int = kvm_mips_dequeue_io_int_cb, |
| 424 | .irq_deliver = kvm_mips_irq_deliver_cb, |
| 425 | .irq_clear = kvm_mips_irq_clear_cb, |
Sanjay Lal | f5c236d | 2012-11-21 18:34:09 -0800 | [diff] [blame] | 426 | }; |
| 427 | |
| 428 | int kvm_mips_emulation_init(struct kvm_mips_callbacks **install_callbacks) |
| 429 | { |
| 430 | *install_callbacks = &kvm_trap_emul_callbacks; |
| 431 | return 0; |
| 432 | } |