18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
38c2ecf20Sopenharmony_ci * License.  See the file "COPYING" in the main directory of this archive
48c2ecf20Sopenharmony_ci * for more details.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * KVM/MIPS: Binary Patching for privileged instructions, reduces traps.
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * Copyright (C) 2012  MIPS Technologies, Inc.  All rights reserved.
98c2ecf20Sopenharmony_ci * Authors: Sanjay Lal <sanjayl@kymasys.com>
108c2ecf20Sopenharmony_ci */
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <linux/errno.h>
138c2ecf20Sopenharmony_ci#include <linux/err.h>
148c2ecf20Sopenharmony_ci#include <linux/highmem.h>
158c2ecf20Sopenharmony_ci#include <linux/kvm_host.h>
168c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
178c2ecf20Sopenharmony_ci#include <linux/vmalloc.h>
188c2ecf20Sopenharmony_ci#include <linux/fs.h>
198c2ecf20Sopenharmony_ci#include <linux/memblock.h>
208c2ecf20Sopenharmony_ci#include <asm/cacheflush.h>
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#include "commpage.h"
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci/**
258c2ecf20Sopenharmony_ci * kvm_mips_trans_replace() - Replace trapping instruction in guest memory.
268c2ecf20Sopenharmony_ci * @vcpu:	Virtual CPU.
278c2ecf20Sopenharmony_ci * @opc:	PC of instruction to replace.
288c2ecf20Sopenharmony_ci * @replace:	Instruction to write
298c2ecf20Sopenharmony_ci */
308c2ecf20Sopenharmony_cistatic int kvm_mips_trans_replace(struct kvm_vcpu *vcpu, u32 *opc,
318c2ecf20Sopenharmony_ci				  union mips_instruction replace)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	unsigned long vaddr = (unsigned long)opc;
348c2ecf20Sopenharmony_ci	int err;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ciretry:
378c2ecf20Sopenharmony_ci	/* The GVA page table is still active so use the Linux TLB handlers */
388c2ecf20Sopenharmony_ci	kvm_trap_emul_gva_lockless_begin(vcpu);
398c2ecf20Sopenharmony_ci	err = put_user(replace.word, opc);
408c2ecf20Sopenharmony_ci	kvm_trap_emul_gva_lockless_end(vcpu);
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	if (unlikely(err)) {
438c2ecf20Sopenharmony_ci		/*
448c2ecf20Sopenharmony_ci		 * We write protect clean pages in GVA page table so normal
458c2ecf20Sopenharmony_ci		 * Linux TLB mod handler doesn't silently dirty the page.
468c2ecf20Sopenharmony_ci		 * Its also possible we raced with a GVA invalidation.
478c2ecf20Sopenharmony_ci		 * Try to force the page to become dirty.
488c2ecf20Sopenharmony_ci		 */
498c2ecf20Sopenharmony_ci		err = kvm_trap_emul_gva_fault(vcpu, vaddr, true);
508c2ecf20Sopenharmony_ci		if (unlikely(err)) {
518c2ecf20Sopenharmony_ci			kvm_info("%s: Address unwriteable: %p\n",
528c2ecf20Sopenharmony_ci				 __func__, opc);
538c2ecf20Sopenharmony_ci			return -EFAULT;
548c2ecf20Sopenharmony_ci		}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci		/*
578c2ecf20Sopenharmony_ci		 * Try again. This will likely trigger a TLB refill, which will
588c2ecf20Sopenharmony_ci		 * fetch the new dirty entry from the GVA page table, which
598c2ecf20Sopenharmony_ci		 * should then succeed.
608c2ecf20Sopenharmony_ci		 */
618c2ecf20Sopenharmony_ci		goto retry;
628c2ecf20Sopenharmony_ci	}
638c2ecf20Sopenharmony_ci	__local_flush_icache_user_range(vaddr, vaddr + 4);
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	return 0;
668c2ecf20Sopenharmony_ci}
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ciint kvm_mips_trans_cache_index(union mips_instruction inst, u32 *opc,
698c2ecf20Sopenharmony_ci			       struct kvm_vcpu *vcpu)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	union mips_instruction nop_inst = { 0 };
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	/* Replace the CACHE instruction, with a NOP */
748c2ecf20Sopenharmony_ci	return kvm_mips_trans_replace(vcpu, opc, nop_inst);
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci/*
788c2ecf20Sopenharmony_ci * Address based CACHE instructions are transformed into synci(s). A little
798c2ecf20Sopenharmony_ci * heavy for just D-cache invalidates, but avoids an expensive trap
808c2ecf20Sopenharmony_ci */
818c2ecf20Sopenharmony_ciint kvm_mips_trans_cache_va(union mips_instruction inst, u32 *opc,
828c2ecf20Sopenharmony_ci			    struct kvm_vcpu *vcpu)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	union mips_instruction synci_inst = { 0 };
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	synci_inst.i_format.opcode = bcond_op;
878c2ecf20Sopenharmony_ci	synci_inst.i_format.rs = inst.i_format.rs;
888c2ecf20Sopenharmony_ci	synci_inst.i_format.rt = synci_op;
898c2ecf20Sopenharmony_ci	if (cpu_has_mips_r6)
908c2ecf20Sopenharmony_ci		synci_inst.i_format.simmediate = inst.spec3_format.simmediate;
918c2ecf20Sopenharmony_ci	else
928c2ecf20Sopenharmony_ci		synci_inst.i_format.simmediate = inst.i_format.simmediate;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	return kvm_mips_trans_replace(vcpu, opc, synci_inst);
958c2ecf20Sopenharmony_ci}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ciint kvm_mips_trans_mfc0(union mips_instruction inst, u32 *opc,
988c2ecf20Sopenharmony_ci			struct kvm_vcpu *vcpu)
998c2ecf20Sopenharmony_ci{
1008c2ecf20Sopenharmony_ci	union mips_instruction mfc0_inst = { 0 };
1018c2ecf20Sopenharmony_ci	u32 rd, sel;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	rd = inst.c0r_format.rd;
1048c2ecf20Sopenharmony_ci	sel = inst.c0r_format.sel;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	if (rd == MIPS_CP0_ERRCTL && sel == 0) {
1078c2ecf20Sopenharmony_ci		mfc0_inst.r_format.opcode = spec_op;
1088c2ecf20Sopenharmony_ci		mfc0_inst.r_format.rd = inst.c0r_format.rt;
1098c2ecf20Sopenharmony_ci		mfc0_inst.r_format.func = add_op;
1108c2ecf20Sopenharmony_ci	} else {
1118c2ecf20Sopenharmony_ci		mfc0_inst.i_format.opcode = lw_op;
1128c2ecf20Sopenharmony_ci		mfc0_inst.i_format.rt = inst.c0r_format.rt;
1138c2ecf20Sopenharmony_ci		mfc0_inst.i_format.simmediate = KVM_GUEST_COMMPAGE_ADDR |
1148c2ecf20Sopenharmony_ci			offsetof(struct kvm_mips_commpage, cop0.reg[rd][sel]);
1158c2ecf20Sopenharmony_ci#ifdef CONFIG_CPU_BIG_ENDIAN
1168c2ecf20Sopenharmony_ci		if (sizeof(vcpu->arch.cop0->reg[0][0]) == 8)
1178c2ecf20Sopenharmony_ci			mfc0_inst.i_format.simmediate |= 4;
1188c2ecf20Sopenharmony_ci#endif
1198c2ecf20Sopenharmony_ci	}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	return kvm_mips_trans_replace(vcpu, opc, mfc0_inst);
1228c2ecf20Sopenharmony_ci}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ciint kvm_mips_trans_mtc0(union mips_instruction inst, u32 *opc,
1258c2ecf20Sopenharmony_ci			struct kvm_vcpu *vcpu)
1268c2ecf20Sopenharmony_ci{
1278c2ecf20Sopenharmony_ci	union mips_instruction mtc0_inst = { 0 };
1288c2ecf20Sopenharmony_ci	u32 rd, sel;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	rd = inst.c0r_format.rd;
1318c2ecf20Sopenharmony_ci	sel = inst.c0r_format.sel;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	mtc0_inst.i_format.opcode = sw_op;
1348c2ecf20Sopenharmony_ci	mtc0_inst.i_format.rt = inst.c0r_format.rt;
1358c2ecf20Sopenharmony_ci	mtc0_inst.i_format.simmediate = KVM_GUEST_COMMPAGE_ADDR |
1368c2ecf20Sopenharmony_ci		offsetof(struct kvm_mips_commpage, cop0.reg[rd][sel]);
1378c2ecf20Sopenharmony_ci#ifdef CONFIG_CPU_BIG_ENDIAN
1388c2ecf20Sopenharmony_ci	if (sizeof(vcpu->arch.cop0->reg[0][0]) == 8)
1398c2ecf20Sopenharmony_ci		mtc0_inst.i_format.simmediate |= 4;
1408c2ecf20Sopenharmony_ci#endif
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	return kvm_mips_trans_replace(vcpu, opc, mtc0_inst);
1438c2ecf20Sopenharmony_ci}
144