Commit | Line | Data |
---|---|---|
50c83085 | 1 | /* |
d116e812 DCZ |
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: Binary Patching for privileged instructions, reduces traps. | |
7 | * | |
8 | * Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved. | |
9 | * Authors: Sanjay Lal <sanjayl@kymasys.com> | |
10 | */ | |
50c83085 SL |
11 | |
12 | #include <linux/errno.h> | |
13 | #include <linux/err.h> | |
28cc5bd5 | 14 | #include <linux/highmem.h> |
50c83085 SL |
15 | #include <linux/kvm_host.h> |
16 | #include <linux/module.h> | |
17 | #include <linux/vmalloc.h> | |
18 | #include <linux/fs.h> | |
19 | #include <linux/bootmem.h> | |
facaaec1 | 20 | #include <asm/cacheflush.h> |
50c83085 | 21 | |
d7d5b05f | 22 | #include "commpage.h" |
50c83085 | 23 | |
d5cd26bc JH |
24 | /** |
25 | * kvm_mips_trans_replace() - Replace trapping instruction in guest memory. | |
26 | * @vcpu: Virtual CPU. | |
27 | * @opc: PC of instruction to replace. | |
28 | * @replace: Instruction to write | |
29 | */ | |
258f3a2e JH |
30 | static int kvm_mips_trans_replace(struct kvm_vcpu *vcpu, u32 *opc, |
31 | union mips_instruction replace) | |
d5cd26bc | 32 | { |
28cc5bd5 JH |
33 | unsigned long paddr, flags; |
34 | void *vaddr; | |
d5cd26bc | 35 | |
8296963e | 36 | if (KVM_GUEST_KSEGX((unsigned long)opc) == KVM_GUEST_KSEG0) { |
28cc5bd5 JH |
37 | paddr = kvm_mips_translate_guest_kseg0_to_hpa(vcpu, |
38 | (unsigned long)opc); | |
39 | vaddr = kmap_atomic(pfn_to_page(PHYS_PFN(paddr))); | |
40 | vaddr += paddr & ~PAGE_MASK; | |
41 | memcpy(vaddr, (void *)&replace, sizeof(u32)); | |
42 | local_flush_icache_range((unsigned long)vaddr, | |
43 | (unsigned long)vaddr + 32); | |
44 | kunmap_atomic(vaddr); | |
d5cd26bc JH |
45 | } else if (KVM_GUEST_KSEGX((unsigned long) opc) == KVM_GUEST_KSEG23) { |
46 | local_irq_save(flags); | |
47 | memcpy((void *)opc, (void *)&replace, sizeof(u32)); | |
48 | local_flush_icache_range((unsigned long)opc, | |
49 | (unsigned long)opc + 32); | |
50 | local_irq_restore(flags); | |
51 | } else { | |
52 | kvm_err("%s: Invalid address: %p\n", __func__, opc); | |
53 | return -EFAULT; | |
54 | } | |
55 | ||
56 | return 0; | |
57 | } | |
58 | ||
258f3a2e | 59 | int kvm_mips_trans_cache_index(union mips_instruction inst, u32 *opc, |
d116e812 | 60 | struct kvm_vcpu *vcpu) |
50c83085 | 61 | { |
258f3a2e JH |
62 | union mips_instruction nop_inst = { 0 }; |
63 | ||
50c83085 | 64 | /* Replace the CACHE instruction, with a NOP */ |
258f3a2e | 65 | return kvm_mips_trans_replace(vcpu, opc, nop_inst); |
50c83085 SL |
66 | } |
67 | ||
68 | /* | |
d116e812 DCZ |
69 | * Address based CACHE instructions are transformed into synci(s). A little |
70 | * heavy for just D-cache invalidates, but avoids an expensive trap | |
50c83085 | 71 | */ |
258f3a2e | 72 | int kvm_mips_trans_cache_va(union mips_instruction inst, u32 *opc, |
d116e812 | 73 | struct kvm_vcpu *vcpu) |
50c83085 | 74 | { |
258f3a2e | 75 | union mips_instruction synci_inst = { 0 }; |
50c83085 | 76 | |
258f3a2e JH |
77 | synci_inst.i_format.opcode = bcond_op; |
78 | synci_inst.i_format.rs = inst.i_format.rs; | |
79 | synci_inst.i_format.rt = synci_op; | |
5cc4aafc JH |
80 | if (cpu_has_mips_r6) |
81 | synci_inst.i_format.simmediate = inst.spec3_format.simmediate; | |
82 | else | |
83 | synci_inst.i_format.simmediate = inst.i_format.simmediate; | |
50c83085 | 84 | |
d5cd26bc | 85 | return kvm_mips_trans_replace(vcpu, opc, synci_inst); |
50c83085 SL |
86 | } |
87 | ||
258f3a2e JH |
88 | int kvm_mips_trans_mfc0(union mips_instruction inst, u32 *opc, |
89 | struct kvm_vcpu *vcpu) | |
50c83085 | 90 | { |
258f3a2e JH |
91 | union mips_instruction mfc0_inst = { 0 }; |
92 | u32 rd, sel; | |
50c83085 | 93 | |
258f3a2e JH |
94 | rd = inst.c0r_format.rd; |
95 | sel = inst.c0r_format.sel; | |
50c83085 | 96 | |
258f3a2e JH |
97 | if (rd == MIPS_CP0_ERRCTL && sel == 0) { |
98 | mfc0_inst.r_format.opcode = spec_op; | |
99 | mfc0_inst.r_format.rd = inst.c0r_format.rt; | |
100 | mfc0_inst.r_format.func = add_op; | |
50c83085 | 101 | } else { |
258f3a2e JH |
102 | mfc0_inst.i_format.opcode = lw_op; |
103 | mfc0_inst.i_format.rt = inst.c0r_format.rt; | |
42aa12e7 | 104 | mfc0_inst.i_format.simmediate = KVM_GUEST_COMMPAGE_ADDR | |
258f3a2e | 105 | offsetof(struct kvm_mips_commpage, cop0.reg[rd][sel]); |
5808844f JH |
106 | #ifdef CONFIG_CPU_BIG_ENDIAN |
107 | if (sizeof(vcpu->arch.cop0->reg[0][0]) == 8) | |
108 | mfc0_inst.i_format.simmediate |= 4; | |
109 | #endif | |
50c83085 SL |
110 | } |
111 | ||
d5cd26bc | 112 | return kvm_mips_trans_replace(vcpu, opc, mfc0_inst); |
50c83085 SL |
113 | } |
114 | ||
258f3a2e JH |
115 | int kvm_mips_trans_mtc0(union mips_instruction inst, u32 *opc, |
116 | struct kvm_vcpu *vcpu) | |
50c83085 | 117 | { |
258f3a2e JH |
118 | union mips_instruction mtc0_inst = { 0 }; |
119 | u32 rd, sel; | |
50c83085 | 120 | |
258f3a2e JH |
121 | rd = inst.c0r_format.rd; |
122 | sel = inst.c0r_format.sel; | |
50c83085 | 123 | |
258f3a2e JH |
124 | mtc0_inst.i_format.opcode = sw_op; |
125 | mtc0_inst.i_format.rt = inst.c0r_format.rt; | |
42aa12e7 | 126 | mtc0_inst.i_format.simmediate = KVM_GUEST_COMMPAGE_ADDR | |
258f3a2e | 127 | offsetof(struct kvm_mips_commpage, cop0.reg[rd][sel]); |
5808844f JH |
128 | #ifdef CONFIG_CPU_BIG_ENDIAN |
129 | if (sizeof(vcpu->arch.cop0->reg[0][0]) == 8) | |
130 | mtc0_inst.i_format.simmediate |= 4; | |
131 | #endif | |
50c83085 | 132 | |
d5cd26bc | 133 | return kvm_mips_trans_replace(vcpu, opc, mtc0_inst); |
50c83085 | 134 | } |