Move the instruction decoding to deocde.c and adapt the code slightly to operate on a struct pt_regs structure to make it easier for someone to take and reuse this code in a different setting some time. Signed-off-by: Christoffer Dall <c.dall@xxxxxxxxxxxxxxxxxxxxxx> --- arch/arm/include/asm/kvm_decode.h | 37 +++ arch/arm/include/asm/kvm_emulate.h | 1 arch/arm/include/asm/kvm_host.h | 8 - arch/arm/kvm/Makefile | 2 arch/arm/kvm/decode.c | 463 ++++++++++++++++++++++++++++++++++++ arch/arm/kvm/emulate.c | 438 ---------------------------------- arch/arm/kvm/mmio.c | 13 + 7 files changed, 516 insertions(+), 446 deletions(-) create mode 100644 arch/arm/include/asm/kvm_decode.h create mode 100644 arch/arm/kvm/decode.c diff --git a/arch/arm/include/asm/kvm_decode.h b/arch/arm/include/asm/kvm_decode.h new file mode 100644 index 0000000..6fd1e39 --- /dev/null +++ b/arch/arm/include/asm/kvm_decode.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012 - Virtual Open Systems and Columbia University + * Author: Christoffer Dall <c.dall@xxxxxxxxxxxxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __ARM_KVM_DECODE_H__ +#define __ARM_KVM_DECODE_H__ + +#include <linux/types.h> + +struct kvm_vcpu; +struct kvm_exit_mmio; + +struct kvm_decode { + struct pt_regs *regs; + unsigned long hxfar; + unsigned long rt; + bool sign_extend; +}; + +int kvm_decode_load_store(struct kvm_decode *decode, unsigned long instr, + struct kvm_exit_mmio *mmio); + +#endif /* __ARM_KVM_DECODE_H__ */ diff --git a/arch/arm/include/asm/kvm_emulate.h b/arch/arm/include/asm/kvm_emulate.h index 375795b..83a6fe1 100644 --- a/arch/arm/include/asm/kvm_emulate.h +++ b/arch/arm/include/asm/kvm_emulate.h @@ -33,7 +33,6 @@ void kvm_skip_instr(struct kvm_vcpu *vcpu, bool is_wide_instr); void kvm_inject_undefined(struct kvm_vcpu *vcpu); void kvm_inject_dabt(struct kvm_vcpu *vcpu, unsigned long addr); void kvm_inject_pabt(struct kvm_vcpu *vcpu, unsigned long addr); - static inline u32 *vcpu_pc(struct kvm_vcpu *vcpu) { return (u32 *)&vcpu->arch.regs.usr_regs.ARM_pc; diff --git a/arch/arm/include/asm/kvm_host.h b/arch/arm/include/asm/kvm_host.h index 439deb0..41012ef 100644 --- a/arch/arm/include/asm/kvm_host.h +++ b/arch/arm/include/asm/kvm_host.h @@ -22,6 +22,7 @@ #include <asm/kvm.h> #include <asm/kvm_asm.h> #include <asm/fpstate.h> +#include <asm/kvm_decode.h> #include <asm/kvm_vgic.h> #include <asm/kvm_arch_timer.h> @@ -78,13 +79,6 @@ struct kvm_mmu_memory_cache { void *objects[KVM_NR_MEM_OBJS]; }; -struct kvm_decode { - struct pt_regs *regs; - unsigned long hxfar; - unsigned long rt; /* destination register for loads */ - bool sign_extend; /* for byte/halfword loads */ -}; - struct kvm_vcpu_arch { struct kvm_regs regs; diff --git a/arch/arm/kvm/Makefile b/arch/arm/kvm/Makefile index 6b19e5c..7c5a635 100644 --- a/arch/arm/kvm/Makefile +++ b/arch/arm/kvm/Makefile @@ -19,6 +19,6 @@ obj-$(CONFIG_KVM_ARM_HOST) += init.o interrupts.o obj-$(CONFIG_KVM_ARM_HOST) += $(addprefix ../../../virt/kvm/, kvm_main.o coalesced_mmio.o) obj-$(CONFIG_KVM_ARM_HOST) += arm.o guest.o mmu.o emulate.o reset.o -obj-$(CONFIG_KVM_ARM_HOST) += coproc.o coproc_a15.o mmio.o +obj-$(CONFIG_KVM_ARM_HOST) += coproc.o coproc_a15.o mmio.o decode.o obj-$(CONFIG_KVM_ARM_VGIC) += vgic.o obj-$(CONFIG_KVM_ARM_TIMER) += timer.o diff --git a/arch/arm/kvm/decode.c b/arch/arm/kvm/decode.c new file mode 100644 index 0000000..48e6bef --- /dev/null +++ b/arch/arm/kvm/decode.c @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2012 - Virtual Open Systems and Columbia University + * Author: Christoffer Dall <c.dall@xxxxxxxxxxxxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include <linux/kvm_host.h> +#include <asm/kvm_mmio.h> +#include <asm/kvm_emulate.h> +#include <asm/kvm_decode.h> +#include <trace/events/kvm.h> + +#include "trace.h" + +struct arm_instr { + /* Instruction decoding */ + u32 opc; + u32 opc_mask; + + /* Decoding for the register write back */ + bool register_form; + u32 imm; + u8 Rm; + u8 type; + u8 shift_n; + + /* Common decoding */ + u8 len; + bool sign_extend; + bool w; + + bool (*decode)(struct kvm_decode *decode, struct kvm_exit_mmio *mmio, + unsigned long instr, struct arm_instr *ai); +}; + +enum SRType { + SRType_LSL, + SRType_LSR, + SRType_ASR, + SRType_ROR, + SRType_RRX +}; + +/* Modelled after DecodeImmShift() in the ARM ARM */ +static enum SRType decode_imm_shift(u8 type, u8 imm5, u8 *amount) +{ + switch (type) { + case 0x0: + *amount = imm5; + return SRType_LSL; + case 0x1: + *amount = (imm5 == 0) ? 32 : imm5; + return SRType_LSR; + case 0x2: + *amount = (imm5 == 0) ? 32 : imm5; + return SRType_ASR; + case 0x3: + if (imm5 == 0) { + *amount = 1; + return SRType_RRX; + } else { + *amount = imm5; + return SRType_ROR; + } + } + + return SRType_LSL; +} + +/* Modelled after Shift() in the ARM ARM */ +static u32 shift(u32 value, u8 N, enum SRType type, u8 amount, bool carry_in) +{ + u32 mask = (1 << N) - 1; + s32 svalue = (s32)value; + + BUG_ON(N > 32); + BUG_ON(type == SRType_RRX && amount != 1); + BUG_ON(amount > N); + + if (amount == 0) + return value; + + switch (type) { + case SRType_LSL: + value <<= amount; + break; + case SRType_LSR: + value >>= amount; + break; + case SRType_ASR: + if (value & (1 << (N - 1))) + svalue |= ((-1UL) << N); + value = svalue >> amount; + break; + case SRType_ROR: + value = (value >> amount) | (value << (N - amount)); + break; + case SRType_RRX: { + u32 C = (carry_in) ? 1 : 0; + value = (value >> 1) | (C << (N - 1)); + break; + } + } + + return value & mask; +} + +static bool decode_arm_wb(struct kvm_decode *decode, struct kvm_exit_mmio *mmio, + unsigned long instr, const struct arm_instr *ai) +{ + u8 Rt = (instr >> 12) & 0xf; + u8 Rn = (instr >> 16) & 0xf; + u8 W = (instr >> 21) & 1; + u8 U = (instr >> 23) & 1; + u8 P = (instr >> 24) & 1; + u32 base_addr = decode->regs->uregs[Rn]; + u32 offset_addr, offset; + + /* + * Technically this is allowed in certain circumstances, + * but we don't support it. + */ + if (Rt == 15 || Rn == 15) + return false; + + if (P && !W) { + kvm_err("Decoding operation with valid ISV?\n"); + return false; + } + + decode->rt = Rt; + + if (ai->register_form) { + /* Register operation */ + enum SRType s_type; + u8 shift_n = 0; + bool c_bit = decode->regs->ARM_cpsr & PSR_C_BIT; + u32 s_reg = decode->regs->uregs[ai->Rm]; + + s_type = decode_imm_shift(ai->type, ai->shift_n, &shift_n); + offset = shift(s_reg, 5, s_type, shift_n, c_bit); + } else { + /* Immediate operation */ + offset = ai->imm; + } + + /* Handle Writeback */ + if (U) + offset_addr = base_addr + offset; + else + offset_addr = base_addr - offset; + decode->regs->uregs[Rn] = offset_addr; + return true; +} + +static bool decode_arm_ls(struct kvm_decode *decode, struct kvm_exit_mmio *mmio, + unsigned long instr, struct arm_instr *ai) +{ + u8 A = (instr >> 25) & 1; + + mmio->is_write = ai->w; + mmio->len = ai->len; + decode->sign_extend = false; + + ai->register_form = A; + ai->imm = instr & 0xfff; + ai->Rm = instr & 0xf; + ai->type = (instr >> 5) & 0x3; + ai->shift_n = (instr >> 7) & 0x1f; + + return decode_arm_wb(decode, mmio, instr, ai); +} + +static bool decode_arm_extra(struct kvm_decode *decode, + struct kvm_exit_mmio *mmio, + unsigned long instr, struct arm_instr *ai) +{ + mmio->is_write = ai->w; + mmio->len = ai->len; + decode->sign_extend = ai->sign_extend; + + ai->register_form = !((instr >> 22) & 1); + ai->imm = ((instr >> 4) & 0xf0) | (instr & 0xf); + ai->Rm = instr & 0xf; + ai->type = 0; /* SRType_LSL */ + ai->shift_n = 0; + + return decode_arm_wb(decode, mmio, instr, ai); +} + +/* + * The encodings in this table assumes that a fault was generated where the + * ISV field in the HSR was clear, and the decoding information was invalid, + * which means that a register write-back occurred, the PC was used as the + * destination or a load/store multiple operation was used. Since the latter + * two cases are crazy for MMIO on the guest side, we simply inject a fault + * when this happens and support the common case. + * + * We treat unpriviledged loads and stores of words and bytes like all other + * loads and stores as their encodings mandate the W bit set and the P bit + * clear. + */ +static const struct arm_instr arm_instr[] = { + /**************** Load/Store Word and Byte **********************/ + /* Store word with writeback */ + { .opc = 0x04000000, .opc_mask = 0x0c500000, .len = 4, .w = true, + .sign_extend = false, .decode = decode_arm_ls }, + /* Store byte with writeback */ + { .opc = 0x04400000, .opc_mask = 0x0c500000, .len = 1, .w = true, + .sign_extend = false, .decode = decode_arm_ls }, + /* Load word with writeback */ + { .opc = 0x04100000, .opc_mask = 0x0c500000, .len = 4, .w = false, + .sign_extend = false, .decode = decode_arm_ls }, + /* Load byte with writeback */ + { .opc = 0x04500000, .opc_mask = 0x0c500000, .len = 1, .w = false, + .sign_extend = false, .decode = decode_arm_ls }, + + /*************** Extra load/store instructions ******************/ + + /* Store halfword with writeback */ + { .opc = 0x000000b0, .opc_mask = 0x0c1000f0, .len = 2, .w = true, + .sign_extend = false, .decode = decode_arm_extra }, + /* Load halfword with writeback */ + { .opc = 0x001000b0, .opc_mask = 0x0c1000f0, .len = 2, .w = false, + .sign_extend = false, .decode = decode_arm_extra }, + + /* Load dual with writeback */ + { .opc = 0x000000d0, .opc_mask = 0x0c1000f0, .len = 8, .w = false, + .sign_extend = false, .decode = decode_arm_extra }, + /* Load signed byte with writeback */ + { .opc = 0x001000d0, .opc_mask = 0x0c1000f0, .len = 1, .w = false, + .sign_extend = true, .decode = decode_arm_extra }, + + /* Store dual with writeback */ + { .opc = 0x000000f0, .opc_mask = 0x0c1000f0, .len = 8, .w = true, + .sign_extend = false, .decode = decode_arm_extra }, + /* Load signed halfword with writeback */ + { .opc = 0x001000f0, .opc_mask = 0x0c1000f0, .len = 2, .w = false, + .sign_extend = true, .decode = decode_arm_extra }, + + /* Store halfword unprivileged */ + { .opc = 0x002000b0, .opc_mask = 0x0f3000f0, .len = 2, .w = true, + .sign_extend = false, .decode = decode_arm_extra }, + /* Load halfword unprivileged */ + { .opc = 0x003000b0, .opc_mask = 0x0f3000f0, .len = 2, .w = false, + .sign_extend = false, .decode = decode_arm_extra }, + /* Load signed byte unprivileged */ + { .opc = 0x003000d0, .opc_mask = 0x0f3000f0, .len = 1, .w = false, + .sign_extend = true , .decode = decode_arm_extra }, + /* Load signed halfword unprivileged */ + { .opc = 0x003000d0, .opc_mask = 0x0f3000f0, .len = 2, .w = false, + .sign_extend = true , .decode = decode_arm_extra }, +}; + +static bool kvm_decode_arm_ls(struct kvm_decode *decode, unsigned long instr, + struct kvm_exit_mmio *mmio) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(arm_instr); i++) { + const struct arm_instr *ai = &arm_instr[i]; + if ((instr & ai->opc_mask) == ai->opc) { + struct arm_instr ai_copy = *ai; + return ai->decode(decode, mmio, instr, &ai_copy); + } + } + return false; +} + +struct thumb_instr { + bool is32; + + union { + struct { + u8 opcode; + u8 mask; + } t16; + + struct { + u8 op1; + u8 op2; + u8 op2_mask; + } t32; + }; + + bool (*decode)(struct kvm_decode *decode, struct kvm_exit_mmio *mmio, + unsigned long instr, const struct thumb_instr *ti); +}; + +static bool decode_thumb_wb(struct kvm_decode *decode, + struct kvm_exit_mmio *mmio, + unsigned long instr) +{ + bool P = (instr >> 10) & 1; + bool U = (instr >> 9) & 1; + u8 imm8 = instr & 0xff; + u32 offset_addr = decode->hxfar; + u8 Rn = (instr >> 16) & 0xf; + + decode->rt = (instr >> 12) & 0xf; + + if (Rn == 15) + return false; + + /* Handle Writeback */ + if (!P && U) + decode->regs->uregs[Rn] = offset_addr + imm8; + else if (!P && !U) + decode->regs->uregs[Rn] = offset_addr - imm8; + return true; +} + +static bool decode_thumb_str(struct kvm_decode *decode, + struct kvm_exit_mmio *mmio, + unsigned long instr, const struct thumb_instr *ti) +{ + u8 op1 = (instr >> (16 + 5)) & 0x7; + u8 op2 = (instr >> 6) & 0x3f; + + mmio->is_write = true; + decode->sign_extend = false; + + switch (op1) { + case 0x0: mmio->len = 1; break; + case 0x1: mmio->len = 2; break; + case 0x2: mmio->len = 4; break; + default: + return false; /* Only register write-back versions! */ + } + + if ((op2 & 0x24) == 0x24) { + /* STRB (immediate, thumb, W=1) */ + return decode_thumb_wb(decode, mmio, instr); + } + + return false; +} + +static bool decode_thumb_ldr(struct kvm_decode *decode, + struct kvm_exit_mmio *mmio, + unsigned long instr, const struct thumb_instr *ti) +{ + u8 op1 = (instr >> (16 + 7)) & 0x3; + u8 op2 = (instr >> 6) & 0x3f; + + mmio->is_write = false; + + switch (ti->t32.op2 & 0x7) { + case 0x1: mmio->len = 1; break; + case 0x3: mmio->len = 2; break; + case 0x5: mmio->len = 4; break; + } + + if (op1 == 0x0) + decode->sign_extend = false; + else if (op1 == 0x2 && (ti->t32.op2 & 0x7) != 0x5) + decode->sign_extend = true; + else + return false; /* Only register write-back versions! */ + + if ((op2 & 0x24) == 0x24) { + /* LDR{S}X (immediate, thumb, W=1) */ + return decode_thumb_wb(decode, mmio, instr); + } + + return false; +} + +/* + * We only support instruction decoding for valid reasonable MMIO operations + * where trapping them do not provide sufficient information in the HSR (no + * 16-bit Thumb instructions provide register writeback that we care about). + * + * The following instruciton types are NOT supported for MMIO operations + * despite the HSR not containing decode info: + * - any Load/Store multiple + * - any load/store exclusive + * - any load/store dual + * - anything with the PC as the dest register + */ +static const struct thumb_instr thumb_instr[] = { + /**************** 32-bit Thumb instructions **********************/ + /* Store single data item: Op1 == 11, Op2 == 000xxx0 */ + { .is32 = true, .t32 = { 3, 0x00, 0x71}, decode_thumb_str }, + /* Load byte: Op1 == 11, Op2 == 00xx001 */ + { .is32 = true, .t32 = { 3, 0x01, 0x67}, decode_thumb_ldr }, + /* Load halfword: Op1 == 11, Op2 == 00xx011 */ + { .is32 = true, .t32 = { 3, 0x03, 0x67}, decode_thumb_ldr }, + /* Load word: Op1 == 11, Op2 == 00xx101 */ + { .is32 = true, .t32 = { 3, 0x05, 0x67}, decode_thumb_ldr }, +}; + + + +static bool kvm_decode_thumb_ls(struct kvm_decode *decode, unsigned long instr, + struct kvm_exit_mmio *mmio) +{ + bool is32 = is_wide_instruction(instr); + bool is16 = !is32; + struct thumb_instr tinstr; /* re-use to pass on already decoded info */ + int i; + + if (is16) { + tinstr.t16.opcode = (instr >> 10) & 0x3f; + } else { + tinstr.t32.op1 = (instr >> (16 + 11)) & 0x3; + tinstr.t32.op2 = (instr >> (16 + 4)) & 0x7f; + } + + for (i = 0; i < ARRAY_SIZE(thumb_instr); i++) { + const struct thumb_instr *ti = &thumb_instr[i]; + if (ti->is32 != is32) + continue; + + if (is16) { + if ((tinstr.t16.opcode & ti->t16.mask) != ti->t16.opcode) + continue; + } else { + if (ti->t32.op1 != tinstr.t32.op1) + continue; + if ((ti->t32.op2_mask & tinstr.t32.op2) != ti->t32.op2) + continue; + } + + return ti->decode(decode, mmio, instr, &tinstr); + } + + return false; +} + +/** + * kvm_decode_load_store - decodes load/store instructions + * @decode: reads regs and hxfar, writes rt and sign_extend + * @instr: instruction to decode + * @mmio: fills in len and is_write + * + * Decode load/store instructions with HSR ISV clear. The code assumes that + * this was indeed a KVM fault and therefore assumes registers write back for + * single load/store operations and does not support using the PC as the + * destination register. + */ +int kvm_decode_load_store(struct kvm_decode *decode, unsigned long instr, + struct kvm_exit_mmio *mmio) +{ + bool is_thumb; + + is_thumb = !!(decode->regs->ARM_cpsr & PSR_T_BIT); + if (!is_thumb) + return kvm_decode_arm_ls(decode, instr, mmio) ? 0 : 1; + else + return kvm_decode_thumb_ls(decode, instr, mmio) ? 0 : 1; +} diff --git a/arch/arm/kvm/emulate.c b/arch/arm/kvm/emulate.c index 7cbdc68..fff1199 100644 --- a/arch/arm/kvm/emulate.c +++ b/arch/arm/kvm/emulate.c @@ -20,6 +20,7 @@ #include <linux/kvm_host.h> #include <asm/kvm_arm.h> #include <asm/kvm_emulate.h> +#include <asm/kvm_decode.h> #include <trace/events/kvm.h> #include "trace.h" @@ -283,426 +284,6 @@ out: return ret; } -/****************************************************************************** - * Load-Store instruction emulation - *****************************************************************************/ - -struct arm_instr { - /* Instruction decoding */ - u32 opc; - u32 opc_mask; - - /* Decoding for the register write back */ - bool register_form; - u32 imm; - u8 Rm; - u8 type; - u8 shift_n; - - /* Common decoding */ - u8 len; - bool sign_extend; - bool w; - - bool (*decode)(struct kvm_decode *decode, struct kvm_exit_mmio *mmio, - unsigned long instr, struct arm_instr *ai); -}; - -enum SRType { - SRType_LSL, - SRType_LSR, - SRType_ASR, - SRType_ROR, - SRType_RRX -}; - -/* Modelled after DecodeImmShift() in the ARM ARM */ -enum SRType decode_imm_shift(u8 type, u8 imm5, u8 *amount) -{ - switch (type) { - case 0x0: - *amount = imm5; - return SRType_LSL; - case 0x1: - *amount = (imm5 == 0) ? 32 : imm5; - return SRType_LSR; - case 0x2: - *amount = (imm5 == 0) ? 32 : imm5; - return SRType_ASR; - case 0x3: - if (imm5 == 0) { - *amount = 1; - return SRType_RRX; - } else { - *amount = imm5; - return SRType_ROR; - } - } - - return SRType_LSL; -} - -/* Modelled after Shift() in the ARM ARM */ -u32 shift(u32 value, u8 N, enum SRType type, u8 amount, bool carry_in) -{ - u32 mask = (1 << N) - 1; - s32 svalue = (s32)value; - - BUG_ON(N > 32); - BUG_ON(type == SRType_RRX && amount != 1); - BUG_ON(amount > N); - - if (amount == 0) - return value; - - switch (type) { - case SRType_LSL: - value <<= amount; - break; - case SRType_LSR: - value >>= amount; - break; - case SRType_ASR: - if (value & (1 << (N - 1))) - svalue |= ((-1UL) << N); - value = svalue >> amount; - break; - case SRType_ROR: - value = (value >> amount) | (value << (N - amount)); - break; - case SRType_RRX: { - u32 C = (carry_in) ? 1 : 0; - value = (value >> 1) | (C << (N - 1)); - break; - } - } - - return value & mask; -} - -static bool decode_arm_wb(struct kvm_decode *decode, struct kvm_exit_mmio *mmio, - unsigned long instr, const struct arm_instr *ai) -{ - u8 Rt = (instr >> 12) & 0xf; - u8 Rn = (instr >> 16) & 0xf; - u8 W = (instr >> 21) & 1; - u8 U = (instr >> 23) & 1; - u8 P = (instr >> 24) & 1; - u32 base_addr = decode->regs->uregs[Rn]; - u32 offset_addr, offset; - - /* - * Technically this is allowed in certain circumstances, - * but we don't support it. - */ - if (Rt == 15 || Rn == 15) - return false; - - if (P && !W) { - kvm_err("Decoding operation with valid ISV?\n"); - return false; - } - - decode->rt = Rt; - - if (ai->register_form) { - /* Register operation */ - enum SRType s_type; - u8 shift_n; - bool c_bit = decode->regs->ARM_cpsr & PSR_C_BIT; - u32 s_reg = decode->regs->uregs[ai->Rm]; - - s_type = decode_imm_shift(ai->type, ai->shift_n, &shift_n); - offset = shift(s_reg, 5, s_type, shift_n, c_bit); - } else { - /* Immediate operation */ - offset = ai->imm; - } - - /* Handle Writeback */ - if (U) - offset_addr = base_addr + offset; - else - offset_addr = base_addr - offset; - decode->regs->uregs[Rn] = offset_addr; - return true; -} - -static bool decode_arm_ls(struct kvm_decode *decode, struct kvm_exit_mmio *mmio, - unsigned long instr, struct arm_instr *ai) -{ - u8 A = (instr >> 25) & 1; - - mmio->is_write = ai->w; - mmio->len = ai->len; - decode->sign_extend = false; - - ai->register_form = A; - ai->imm = instr & 0xfff; - ai->Rm = instr & 0xf; - ai->type = (instr >> 5) & 0x3; - ai->shift_n = (instr >> 7) & 0x1f; - - return decode_arm_wb(decode, mmio, instr, ai); -} - -static bool decode_arm_extra(struct kvm_decode *decode, - struct kvm_exit_mmio *mmio, - unsigned long instr, struct arm_instr *ai) -{ - mmio->is_write = ai->w; - mmio->len = ai->len; - decode->sign_extend = ai->sign_extend; - - ai->register_form = !((instr >> 22) & 1); - ai->imm = ((instr >> 4) & 0xf0) | (instr & 0xf); - ai->Rm = instr & 0xf; - ai->type = 0; /* SRType_LSL */ - ai->shift_n = 0; - - return decode_arm_wb(decode, mmio, instr, ai); -} - -/* - * The encodings in this table assumes that a fault was generated where the - * ISV field in the HSR was clear, and the decoding information was invalid, - * which means that a register write-back occurred, the PC was used as the - * destination or a load/store multiple operation was used. Since the latter - * two cases are crazy for MMIO on the guest side, we simply inject a fault - * when this happens and support the common case. - * - * We treat unpriviledged loads and stores of words and bytes like all other - * loads and stores as their encodings mandate the W bit set and the P bit - * clear. - */ -static const struct arm_instr arm_instr[] = { - /**************** Load/Store Word and Byte **********************/ - /* Store word with writeback */ - { .opc = 0x04000000, .opc_mask = 0x0c500000, .len = 4, .w = true, - .sign_extend = false, .decode = decode_arm_ls }, - /* Store byte with writeback */ - { .opc = 0x04400000, .opc_mask = 0x0c500000, .len = 1, .w = true, - .sign_extend = false, .decode = decode_arm_ls }, - /* Load word with writeback */ - { .opc = 0x04100000, .opc_mask = 0x0c500000, .len = 4, .w = false, - .sign_extend = false, .decode = decode_arm_ls }, - /* Load byte with writeback */ - { .opc = 0x04500000, .opc_mask = 0x0c500000, .len = 1, .w = false, - .sign_extend = false, .decode = decode_arm_ls }, - - /*************** Extra load/store instructions ******************/ - - /* Store halfword with writeback */ - { .opc = 0x000000b0, .opc_mask = 0x0c1000f0, .len = 2, .w = true, - .sign_extend = false, .decode = decode_arm_extra }, - /* Load halfword with writeback */ - { .opc = 0x001000b0, .opc_mask = 0x0c1000f0, .len = 2, .w = false, - .sign_extend = false, .decode = decode_arm_extra }, - - /* Load dual with writeback */ - { .opc = 0x000000d0, .opc_mask = 0x0c1000f0, .len = 8, .w = false, - .sign_extend = false, .decode = decode_arm_extra }, - /* Load signed byte with writeback */ - { .opc = 0x001000d0, .opc_mask = 0x0c1000f0, .len = 1, .w = false, - .sign_extend = true, .decode = decode_arm_extra }, - - /* Store dual with writeback */ - { .opc = 0x000000f0, .opc_mask = 0x0c1000f0, .len = 8, .w = true, - .sign_extend = false, .decode = decode_arm_extra }, - /* Load signed halfword with writeback */ - { .opc = 0x001000f0, .opc_mask = 0x0c1000f0, .len = 2, .w = false, - .sign_extend = true, .decode = decode_arm_extra }, - - /* Store halfword unprivileged */ - { .opc = 0x002000b0, .opc_mask = 0x0f3000f0, .len = 2, .w = true, - .sign_extend = false, .decode = decode_arm_extra }, - /* Load halfword unprivileged */ - { .opc = 0x003000b0, .opc_mask = 0x0f3000f0, .len = 2, .w = false, - .sign_extend = false, .decode = decode_arm_extra }, - /* Load signed byte unprivileged */ - { .opc = 0x003000d0, .opc_mask = 0x0f3000f0, .len = 1, .w = false, - .sign_extend = true , .decode = decode_arm_extra }, - /* Load signed halfword unprivileged */ - { .opc = 0x003000d0, .opc_mask = 0x0f3000f0, .len = 2, .w = false, - .sign_extend = true , .decode = decode_arm_extra }, -}; - -static bool kvm_decode_arm_ls(struct kvm_decode *decode, unsigned long instr, - struct kvm_exit_mmio *mmio) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(arm_instr); i++) { - const struct arm_instr *ai = &arm_instr[i]; - if ((instr & ai->opc_mask) == ai->opc) { - struct arm_instr ai_copy = *ai; - return ai->decode(decode, mmio, instr, &ai_copy); - } - } - return false; -} - -struct thumb_instr { - bool is32; - - union { - struct { - u8 opcode; - u8 mask; - } t16; - - struct { - u8 op1; - u8 op2; - u8 op2_mask; - } t32; - }; - - bool (*decode)(struct kvm_decode *decode, struct kvm_exit_mmio *mmio, - unsigned long instr, const struct thumb_instr *ti); -}; - -static bool decode_thumb_wb(struct kvm_decode *decode, - struct kvm_exit_mmio *mmio, - unsigned long instr) -{ - bool P = (instr >> 10) & 1; - bool U = (instr >> 9) & 1; - u8 imm8 = instr & 0xff; - u32 offset_addr = decode->hxfar; - u8 Rn = (instr >> 16) & 0xf; - - decode->rt = (instr >> 12) & 0xf; - - if (Rn == 15) - return false; - - /* Handle Writeback */ - if (!P && U) - decode->regs->uregs[Rn] = offset_addr + imm8; - else if (!P && !U) - decode->regs->uregs[Rn] = offset_addr - imm8; - return true; -} - -static bool decode_thumb_str(struct kvm_decode *decode, - struct kvm_exit_mmio *mmio, - unsigned long instr, const struct thumb_instr *ti) -{ - u8 op1 = (instr >> (16 + 5)) & 0x7; - u8 op2 = (instr >> 6) & 0x3f; - - mmio->is_write = true; - decode->sign_extend = false; - - switch (op1) { - case 0x0: mmio->len = 1; break; - case 0x1: mmio->len = 2; break; - case 0x2: mmio->len = 4; break; - default: - return false; /* Only register write-back versions! */ - } - - if ((op2 & 0x24) == 0x24) { - /* STRB (immediate, thumb, W=1) */ - return decode_thumb_wb(decode, mmio, instr); - } - - return false; -} - -static bool decode_thumb_ldr(struct kvm_decode *decode, - struct kvm_exit_mmio *mmio, - unsigned long instr, const struct thumb_instr *ti) -{ - u8 op1 = (instr >> (16 + 7)) & 0x3; - u8 op2 = (instr >> 6) & 0x3f; - - mmio->is_write = false; - - switch (ti->t32.op2 & 0x7) { - case 0x1: mmio->len = 1; break; - case 0x3: mmio->len = 2; break; - case 0x5: mmio->len = 4; break; - } - - if (op1 == 0x0) - decode->sign_extend = false; - else if (op1 == 0x2 && (ti->t32.op2 & 0x7) != 0x5) - decode->sign_extend = true; - else - return false; /* Only register write-back versions! */ - - if ((op2 & 0x24) == 0x24) { - /* LDR{S}X (immediate, thumb, W=1) */ - return decode_thumb_wb(decode, mmio, instr); - } - - return false; -} - -/* - * We only support instruction decoding for valid reasonable MMIO operations - * where trapping them do not provide sufficient information in the HSR (no - * 16-bit Thumb instructions provide register writeback that we care about). - * - * The following instruciton types are NOT supported for MMIO operations - * despite the HSR not containing decode info: - * - any Load/Store multiple - * - any load/store exclusive - * - any load/store dual - * - anything with the PC as the dest register - */ -static const struct thumb_instr thumb_instr[] = { - /**************** 32-bit Thumb instructions **********************/ - /* Store single data item: Op1 == 11, Op2 == 000xxx0 */ - { .is32 = true, .t32 = { 3, 0x00, 0x71}, decode_thumb_str }, - /* Load byte: Op1 == 11, Op2 == 00xx001 */ - { .is32 = true, .t32 = { 3, 0x01, 0x67}, decode_thumb_ldr }, - /* Load halfword: Op1 == 11, Op2 == 00xx011 */ - { .is32 = true, .t32 = { 3, 0x03, 0x67}, decode_thumb_ldr }, - /* Load word: Op1 == 11, Op2 == 00xx101 */ - { .is32 = true, .t32 = { 3, 0x05, 0x67}, decode_thumb_ldr }, -}; - - - -static bool kvm_decode_thumb_ls(struct kvm_decode *decode, unsigned long instr, - struct kvm_exit_mmio *mmio) -{ - bool is32 = is_wide_instruction(instr); - bool is16 = !is32; - struct thumb_instr tinstr; /* re-use to pass on already decoded info */ - int i; - - if (is16) { - tinstr.t16.opcode = (instr >> 10) & 0x3f; - } else { - tinstr.t32.op1 = (instr >> (16 + 11)) & 0x3; - tinstr.t32.op2 = (instr >> (16 + 4)) & 0x7f; - } - - for (i = 0; i < ARRAY_SIZE(thumb_instr); i++) { - const struct thumb_instr *ti = &thumb_instr[i]; - if (ti->is32 != is32) - continue; - - if (is16) { - if ((tinstr.t16.opcode & ti->t16.mask) != ti->t16.opcode) - continue; - } else { - if (ti->t32.op1 != tinstr.t32.op1) - continue; - if ((ti->t32.op2_mask & tinstr.t32.op2) != ti->t32.op2) - continue; - } - - return ti->decode(decode, mmio, instr, &tinstr); - } - - return false; -} - /** * kvm_emulate_mmio_ls - emulates load/store instructions made to I/O memory * @vcpu: The vcpu pointer @@ -722,10 +303,10 @@ static bool kvm_decode_thumb_ls(struct kvm_decode *decode, unsigned long instr, int kvm_emulate_mmio_ls(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa, struct kvm_exit_mmio *mmio) { - bool is_thumb; unsigned long instr = 0; struct pt_regs current_regs; struct kvm_decode *decode = &vcpu->arch.mmio_decode; + int ret; trace_kvm_mmio_emulate(*vcpu_pc(vcpu), instr, *vcpu_cpsr(vcpu)); @@ -741,20 +322,13 @@ int kvm_emulate_mmio_ls(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa, decode->regs = ¤t_regs; decode->hxfar = vcpu->arch.hxfar; - - is_thumb = !!(*vcpu_cpsr(vcpu) & PSR_T_BIT); - if (!is_thumb && !kvm_decode_arm_ls(decode, instr, mmio)) { - kvm_debug("Unable to decode inst: %#08lx (cpsr: %#08x (T=0)" + ret = kvm_decode_load_store(decode, instr, mmio); + if (ret) { + kvm_debug("Insrn. decode error: %#08lx (cpsr: %#08x" "pc: %#08x)\n", instr, *vcpu_cpsr(vcpu), *vcpu_pc(vcpu)); kvm_inject_dabt(vcpu, vcpu->arch.hxfar); - return 1; - } else if (is_thumb && !kvm_decode_thumb_ls(decode, instr, mmio)) { - kvm_debug("Unable to decode inst: %#08lx (cpsr: %#08x (T=1)" - "pc: %#08x)\n", - instr, *vcpu_cpsr(vcpu), *vcpu_pc(vcpu)); - kvm_inject_dabt(vcpu, vcpu->arch.hxfar); - return 1; + return ret; } memcpy(&vcpu->arch.regs.usr_regs, ¤t_regs, sizeof(current_regs)); diff --git a/arch/arm/kvm/mmio.c b/arch/arm/kvm/mmio.c index eefbead..ed6b441 100644 --- a/arch/arm/kvm/mmio.c +++ b/arch/arm/kvm/mmio.c @@ -18,6 +18,7 @@ #include <asm/kvm_mmio.h> #include <asm/kvm_emulate.h> +#include <asm/kvm_decode.h> #include <trace/events/kvm.h> #include "trace.h" @@ -129,13 +130,15 @@ int io_mem_abort(struct kvm_vcpu *vcpu, struct kvm_run *run, * space do its magic. */ - if (vcpu->arch.hsr & HSR_ISV) + if (vcpu->arch.hsr & HSR_ISV) { ret = decode_hsr(vcpu, fault_ipa, &mmio); - else + if (ret) + return ret; + } else { ret = kvm_emulate_mmio_ls(vcpu, fault_ipa, &mmio); - - if (ret != 0) - return ret; + if (ret) + return ret; + } rt = vcpu->arch.mmio_decode.rt; trace_kvm_mmio((mmio.is_write) ? KVM_TRACE_MMIO_WRITE : _______________________________________________ kvmarm mailing list kvmarm@xxxxxxxxxxxxxxxxxxxxx https://lists.cs.columbia.edu/cucslists/listinfo/kvmarm