Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | /* * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * * KVM/MIPS: Binary Patching for privileged instructions, reduces traps. * * Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved. * Authors: Sanjay Lal <sanjayl@kymasys.com> */ #include <linux/errno.h> #include <linux/err.h> #include <linux/kvm_host.h> #include <linux/module.h> #include <linux/vmalloc.h> #include <linux/fs.h> #include <linux/bootmem.h> #include <asm/cacheflush.h> #include "commpage.h" #define SYNCI_TEMPLATE 0x041f0000 #define SYNCI_BASE(x) (((x) >> 21) & 0x1f) #define SYNCI_OFFSET ((x) & 0xffff) #define LW_TEMPLATE 0x8c000000 #define CLEAR_TEMPLATE 0x00000020 #define SW_TEMPLATE 0xac000000 int kvm_mips_trans_cache_index(uint32_t inst, uint32_t *opc, struct kvm_vcpu *vcpu) { int result = 0; unsigned long kseg0_opc; uint32_t synci_inst = 0x0; /* Replace the CACHE instruction, with a NOP */ kseg0_opc = CKSEG0ADDR(kvm_mips_translate_guest_kseg0_to_hpa (vcpu, (unsigned long) opc)); memcpy((void *)kseg0_opc, (void *)&synci_inst, sizeof(uint32_t)); local_flush_icache_range(kseg0_opc, kseg0_opc + 32); return result; } /* * Address based CACHE instructions are transformed into synci(s). A little * heavy for just D-cache invalidates, but avoids an expensive trap */ int kvm_mips_trans_cache_va(uint32_t inst, uint32_t *opc, struct kvm_vcpu *vcpu) { int result = 0; unsigned long kseg0_opc; uint32_t synci_inst = SYNCI_TEMPLATE, base, offset; base = (inst >> 21) & 0x1f; offset = inst & 0xffff; synci_inst |= (base << 21); synci_inst |= offset; kseg0_opc = CKSEG0ADDR(kvm_mips_translate_guest_kseg0_to_hpa (vcpu, (unsigned long) opc)); memcpy((void *)kseg0_opc, (void *)&synci_inst, sizeof(uint32_t)); local_flush_icache_range(kseg0_opc, kseg0_opc + 32); return result; } int kvm_mips_trans_mfc0(uint32_t inst, uint32_t *opc, struct kvm_vcpu *vcpu) { int32_t rt, rd, sel; uint32_t mfc0_inst; unsigned long kseg0_opc, flags; rt = (inst >> 16) & 0x1f; rd = (inst >> 11) & 0x1f; sel = inst & 0x7; if ((rd == MIPS_CP0_ERRCTL) && (sel == 0)) { mfc0_inst = CLEAR_TEMPLATE; mfc0_inst |= ((rt & 0x1f) << 16); } else { mfc0_inst = LW_TEMPLATE; mfc0_inst |= ((rt & 0x1f) << 16); mfc0_inst |= offsetof(struct mips_coproc, reg[rd][sel]) + offsetof(struct kvm_mips_commpage, cop0); } if (KVM_GUEST_KSEGX(opc) == KVM_GUEST_KSEG0) { kseg0_opc = CKSEG0ADDR(kvm_mips_translate_guest_kseg0_to_hpa (vcpu, (unsigned long) opc)); memcpy((void *)kseg0_opc, (void *)&mfc0_inst, sizeof(uint32_t)); local_flush_icache_range(kseg0_opc, kseg0_opc + 32); } else if (KVM_GUEST_KSEGX((unsigned long) opc) == KVM_GUEST_KSEG23) { local_irq_save(flags); memcpy((void *)opc, (void *)&mfc0_inst, sizeof(uint32_t)); local_flush_icache_range((unsigned long)opc, (unsigned long)opc + 32); local_irq_restore(flags); } else { kvm_err("%s: Invalid address: %p\n", __func__, opc); return -EFAULT; } return 0; } int kvm_mips_trans_mtc0(uint32_t inst, uint32_t *opc, struct kvm_vcpu *vcpu) { int32_t rt, rd, sel; uint32_t mtc0_inst = SW_TEMPLATE; unsigned long kseg0_opc, flags; rt = (inst >> 16) & 0x1f; rd = (inst >> 11) & 0x1f; sel = inst & 0x7; mtc0_inst |= ((rt & 0x1f) << 16); mtc0_inst |= offsetof(struct mips_coproc, reg[rd][sel]) + offsetof(struct kvm_mips_commpage, cop0); if (KVM_GUEST_KSEGX(opc) == KVM_GUEST_KSEG0) { kseg0_opc = CKSEG0ADDR(kvm_mips_translate_guest_kseg0_to_hpa (vcpu, (unsigned long) opc)); memcpy((void *)kseg0_opc, (void *)&mtc0_inst, sizeof(uint32_t)); local_flush_icache_range(kseg0_opc, kseg0_opc + 32); } else if (KVM_GUEST_KSEGX((unsigned long) opc) == KVM_GUEST_KSEG23) { local_irq_save(flags); memcpy((void *)opc, (void *)&mtc0_inst, sizeof(uint32_t)); local_flush_icache_range((unsigned long)opc, (unsigned long)opc + 32); local_irq_restore(flags); } else { kvm_err("%s: Invalid address: %p\n", __func__, opc); return -EFAULT; } return 0; } |