Skip to content

Commit 2de451a

Browse files
kristina-martsenkooupton
authored andcommitted
KVM: arm64: Add handler for MOPS exceptions
An Armv8.8 FEAT_MOPS main or epilogue instruction will take an exception if executed on a CPU with a different MOPS implementation option (A or B) than the CPU where the preceding prologue instruction ran. In this case the OS exception handler is expected to reset the registers and restart execution from the prologue instruction. A KVM guest may use the instructions at EL1 at times when the guest is not able to handle the exception, expecting that the instructions will only run on one CPU (e.g. when running UEFI boot services in the guest). As KVM may reschedule the guest between different types of CPUs at any time (on an asymmetric system), it needs to also handle the resulting exception itself in case the guest is not able to. A similar situation will also occur in the future when live migrating a guest from one type of CPU to another. Add handling for the MOPS exception to KVM. The handling can be shared with the EL0 exception handler, as the logic and register layouts are the same. The exception can be handled right after exiting a guest, which avoids the cost of returning to the host exit handler. Similarly to the EL0 exception handler, in case the main or epilogue instruction is being single stepped, it makes sense to finish the step before executing the prologue instruction, so advance the single step state machine. Signed-off-by: Kristina Martsenko <kristina.martsenko@arm.com> Reviewed-by: Marc Zyngier <maz@kernel.org> Link: https://lore.kernel.org/r/20230922112508.1774352-2-kristina.martsenko@arm.com Signed-off-by: Oliver Upton <oliver.upton@linux.dev>
1 parent dafa493 commit 2de451a

File tree

5 files changed

+73
-49
lines changed

5 files changed

+73
-49
lines changed

arch/arm64/include/asm/traps.h

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@
99

1010
#include <linux/list.h>
1111
#include <asm/esr.h>
12+
#include <asm/ptrace.h>
1213
#include <asm/sections.h>
1314

14-
struct pt_regs;
15-
1615
#ifdef CONFIG_ARMV8_DEPRECATED
1716
bool try_emulate_armv8_deprecated(struct pt_regs *regs, u32 insn);
1817
#else
@@ -101,4 +100,55 @@ static inline unsigned long arm64_ras_serror_get_severity(unsigned long esr)
101100

102101
bool arm64_is_fatal_ras_serror(struct pt_regs *regs, unsigned long esr);
103102
void __noreturn arm64_serror_panic(struct pt_regs *regs, unsigned long esr);
103+
104+
static inline void arm64_mops_reset_regs(struct user_pt_regs *regs, unsigned long esr)
105+
{
106+
bool wrong_option = esr & ESR_ELx_MOPS_ISS_WRONG_OPTION;
107+
bool option_a = esr & ESR_ELx_MOPS_ISS_OPTION_A;
108+
int dstreg = ESR_ELx_MOPS_ISS_DESTREG(esr);
109+
int srcreg = ESR_ELx_MOPS_ISS_SRCREG(esr);
110+
int sizereg = ESR_ELx_MOPS_ISS_SIZEREG(esr);
111+
unsigned long dst, src, size;
112+
113+
dst = regs->regs[dstreg];
114+
src = regs->regs[srcreg];
115+
size = regs->regs[sizereg];
116+
117+
/*
118+
* Put the registers back in the original format suitable for a
119+
* prologue instruction, using the generic return routine from the
120+
* Arm ARM (DDI 0487I.a) rules CNTMJ and MWFQH.
121+
*/
122+
if (esr & ESR_ELx_MOPS_ISS_MEM_INST) {
123+
/* SET* instruction */
124+
if (option_a ^ wrong_option) {
125+
/* Format is from Option A; forward set */
126+
regs->regs[dstreg] = dst + size;
127+
regs->regs[sizereg] = -size;
128+
}
129+
} else {
130+
/* CPY* instruction */
131+
if (!(option_a ^ wrong_option)) {
132+
/* Format is from Option B */
133+
if (regs->pstate & PSR_N_BIT) {
134+
/* Backward copy */
135+
regs->regs[dstreg] = dst - size;
136+
regs->regs[srcreg] = src - size;
137+
}
138+
} else {
139+
/* Format is from Option A */
140+
if (size & BIT(63)) {
141+
/* Forward copy */
142+
regs->regs[dstreg] = dst + size;
143+
regs->regs[srcreg] = src + size;
144+
regs->regs[sizereg] = -size;
145+
}
146+
}
147+
}
148+
149+
if (esr & ESR_ELx_MOPS_ISS_FROM_EPILOGUE)
150+
regs->pc -= 8;
151+
else
152+
regs->pc -= 4;
153+
}
104154
#endif

arch/arm64/kernel/traps.c

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -516,53 +516,7 @@ void do_el1_fpac(struct pt_regs *regs, unsigned long esr)
516516

517517
void do_el0_mops(struct pt_regs *regs, unsigned long esr)
518518
{
519-
bool wrong_option = esr & ESR_ELx_MOPS_ISS_WRONG_OPTION;
520-
bool option_a = esr & ESR_ELx_MOPS_ISS_OPTION_A;
521-
int dstreg = ESR_ELx_MOPS_ISS_DESTREG(esr);
522-
int srcreg = ESR_ELx_MOPS_ISS_SRCREG(esr);
523-
int sizereg = ESR_ELx_MOPS_ISS_SIZEREG(esr);
524-
unsigned long dst, src, size;
525-
526-
dst = pt_regs_read_reg(regs, dstreg);
527-
src = pt_regs_read_reg(regs, srcreg);
528-
size = pt_regs_read_reg(regs, sizereg);
529-
530-
/*
531-
* Put the registers back in the original format suitable for a
532-
* prologue instruction, using the generic return routine from the
533-
* Arm ARM (DDI 0487I.a) rules CNTMJ and MWFQH.
534-
*/
535-
if (esr & ESR_ELx_MOPS_ISS_MEM_INST) {
536-
/* SET* instruction */
537-
if (option_a ^ wrong_option) {
538-
/* Format is from Option A; forward set */
539-
pt_regs_write_reg(regs, dstreg, dst + size);
540-
pt_regs_write_reg(regs, sizereg, -size);
541-
}
542-
} else {
543-
/* CPY* instruction */
544-
if (!(option_a ^ wrong_option)) {
545-
/* Format is from Option B */
546-
if (regs->pstate & PSR_N_BIT) {
547-
/* Backward copy */
548-
pt_regs_write_reg(regs, dstreg, dst - size);
549-
pt_regs_write_reg(regs, srcreg, src - size);
550-
}
551-
} else {
552-
/* Format is from Option A */
553-
if (size & BIT(63)) {
554-
/* Forward copy */
555-
pt_regs_write_reg(regs, dstreg, dst + size);
556-
pt_regs_write_reg(regs, srcreg, src + size);
557-
pt_regs_write_reg(regs, sizereg, -size);
558-
}
559-
}
560-
}
561-
562-
if (esr & ESR_ELx_MOPS_ISS_FROM_EPILOGUE)
563-
regs->pc -= 8;
564-
else
565-
regs->pc -= 4;
519+
arm64_mops_reset_regs(&regs->user_regs, esr);
566520

567521
/*
568522
* If single stepping then finish the step before executing the

arch/arm64/kvm/hyp/include/hyp/switch.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <asm/fpsimd.h>
3131
#include <asm/debug-monitors.h>
3232
#include <asm/processor.h>
33+
#include <asm/traps.h>
3334

3435
struct kvm_exception_table_entry {
3536
int insn, fixup;
@@ -265,6 +266,22 @@ static inline bool __populate_fault_info(struct kvm_vcpu *vcpu)
265266
return __get_fault_info(vcpu->arch.fault.esr_el2, &vcpu->arch.fault);
266267
}
267268

269+
static bool kvm_hyp_handle_mops(struct kvm_vcpu *vcpu, u64 *exit_code)
270+
{
271+
*vcpu_pc(vcpu) = read_sysreg_el2(SYS_ELR);
272+
arm64_mops_reset_regs(vcpu_gp_regs(vcpu), vcpu->arch.fault.esr_el2);
273+
write_sysreg_el2(*vcpu_pc(vcpu), SYS_ELR);
274+
275+
/*
276+
* Finish potential single step before executing the prologue
277+
* instruction.
278+
*/
279+
*vcpu_cpsr(vcpu) &= ~DBG_SPSR_SS;
280+
write_sysreg_el2(*vcpu_cpsr(vcpu), SYS_SPSR);
281+
282+
return true;
283+
}
284+
268285
static inline void __hyp_sve_restore_guest(struct kvm_vcpu *vcpu)
269286
{
270287
sve_cond_update_zcr_vq(vcpu_sve_max_vq(vcpu) - 1, SYS_ZCR_EL2);

arch/arm64/kvm/hyp/nvhe/switch.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ static const exit_handler_fn hyp_exit_handlers[] = {
192192
[ESR_ELx_EC_DABT_LOW] = kvm_hyp_handle_dabt_low,
193193
[ESR_ELx_EC_WATCHPT_LOW] = kvm_hyp_handle_watchpt_low,
194194
[ESR_ELx_EC_PAC] = kvm_hyp_handle_ptrauth,
195+
[ESR_ELx_EC_MOPS] = kvm_hyp_handle_mops,
195196
};
196197

197198
static const exit_handler_fn pvm_exit_handlers[] = {
@@ -203,6 +204,7 @@ static const exit_handler_fn pvm_exit_handlers[] = {
203204
[ESR_ELx_EC_DABT_LOW] = kvm_hyp_handle_dabt_low,
204205
[ESR_ELx_EC_WATCHPT_LOW] = kvm_hyp_handle_watchpt_low,
205206
[ESR_ELx_EC_PAC] = kvm_hyp_handle_ptrauth,
207+
[ESR_ELx_EC_MOPS] = kvm_hyp_handle_mops,
206208
};
207209

208210
static const exit_handler_fn *kvm_get_exit_handler_array(struct kvm_vcpu *vcpu)

arch/arm64/kvm/hyp/vhe/switch.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ static const exit_handler_fn hyp_exit_handlers[] = {
126126
[ESR_ELx_EC_DABT_LOW] = kvm_hyp_handle_dabt_low,
127127
[ESR_ELx_EC_WATCHPT_LOW] = kvm_hyp_handle_watchpt_low,
128128
[ESR_ELx_EC_PAC] = kvm_hyp_handle_ptrauth,
129+
[ESR_ELx_EC_MOPS] = kvm_hyp_handle_mops,
129130
};
130131

131132
static const exit_handler_fn *kvm_get_exit_handler_array(struct kvm_vcpu *vcpu)

0 commit comments

Comments
 (0)