The branch main has been updated by markj: URL: https://cgit.FreeBSD.org/src/commit/?id=75cb949228bb97e49f35fd2b2585293a6b9212aa
commit 75cb949228bb97e49f35fd2b2585293a6b9212aa Author: Mark Johnston <ma...@freebsd.org> AuthorDate: 2024-06-04 18:58:08 +0000 Commit: Mark Johnston <ma...@freebsd.org> CommitDate: 2024-06-04 18:58:08 +0000 arm64/vmm: Add breakpoint and single-stepping support This will be used to implement parts of bhyve's gdb stub. Three VM capabilities are added, similar to amd64 without monitor mode. Two cause breakpoint and single-step exceptions to be raised to EL2 and then down to bhyve. One lets the gdb stub mask hardware interrupts while single-stepping, since otherwise the guest will handle a timer interrupt before executing the target instruction and thus fail to make progress. Reviewed by: bnovkov, andrew Sponsored by: Innovate UK Differential Revision: https://reviews.freebsd.org/D44739 --- sys/arm64/include/vmm.h | 6 +++- sys/arm64/vmm/arm64.h | 9 ++++++ sys/arm64/vmm/vmm_arm64.c | 79 +++++++++++++++++++++++++++++++++++++++++++++-- sys/arm64/vmm/vmm_stat.c | 2 ++ sys/arm64/vmm/vmm_stat.h | 2 ++ 5 files changed, 95 insertions(+), 3 deletions(-) diff --git a/sys/arm64/include/vmm.h b/sys/arm64/include/vmm.h index 8e2c9c868635..c06d2ad947e4 100644 --- a/sys/arm64/include/vmm.h +++ b/sys/arm64/include/vmm.h @@ -295,9 +295,11 @@ struct vre { */ enum vm_cap_type { VM_CAP_HALT_EXIT, - VM_CAP_MTRAP_EXIT, VM_CAP_PAUSE_EXIT, VM_CAP_UNRESTRICTED_GUEST, + VM_CAP_BRK_EXIT, + VM_CAP_SS_EXIT, + VM_CAP_MASK_HWINTR, VM_CAP_MAX }; @@ -312,6 +314,8 @@ enum vm_exitcode { VM_EXITCODE_PAGING, VM_EXITCODE_SMCCC, VM_EXITCODE_DEBUG, + VM_EXITCODE_BRK, + VM_EXITCODE_SS, VM_EXITCODE_MAX }; diff --git a/sys/arm64/vmm/arm64.h b/sys/arm64/vmm/arm64.h index 43459d14e143..8cfe77dcde6f 100644 --- a/sys/arm64/vmm/arm64.h +++ b/sys/arm64/vmm/arm64.h @@ -39,6 +39,9 @@ struct vgic_v3; struct vgic_v3_cpu; +/* + * Per-vCPU hypervisor state. + */ struct hypctx { struct trapframe tf; @@ -104,6 +107,12 @@ struct hypctx { struct vtimer_cpu vtimer_cpu; + uint64_t setcaps; /* Currently enabled capabilities. */ + + /* vCPU state used to handle guest debugging. */ + uint64_t debug_spsr; /* Saved guest SPSR */ + uint64_t debug_mdscr; /* Saved guest MDSCR */ + struct vgic_v3_regs vgic_v3_regs; struct vgic_v3_cpu *vgic_cpu; bool has_exception; diff --git a/sys/arm64/vmm/vmm_arm64.c b/sys/arm64/vmm/vmm_arm64.c index e71761f9ccef..e0547bcef914 100644 --- a/sys/arm64/vmm/vmm_arm64.c +++ b/sys/arm64/vmm/vmm_arm64.c @@ -700,7 +700,14 @@ handle_el1_sync_excp(struct hypctx *hypctx, struct vm_exit *vme_ret, arm64_gen_reg_emul_data(esr_iss, vme_ret); vme_ret->exitcode = VM_EXITCODE_REG_EMUL; break; - + case EXCP_BRK: + vmm_stat_incr(hypctx->vcpu, VMEXIT_BRK, 1); + vme_ret->exitcode = VM_EXITCODE_BRK; + break; + case EXCP_SOFTSTP_EL0: + vmm_stat_incr(hypctx->vcpu, VMEXIT_SS, 1); + vme_ret->exitcode = VM_EXITCODE_SS; + break; case EXCP_INSN_ABORT_L: case EXCP_DATA_ABORT_L: vmm_stat_incr(hypctx->vcpu, esr_ec == EXCP_DATA_ABORT_L ? @@ -1313,6 +1320,7 @@ vmmops_exception(void *vcpui, uint64_t esr, uint64_t far) int vmmops_getcap(void *vcpui, int num, int *retval) { + struct hypctx *hypctx = vcpui; int ret; ret = ENOENT; @@ -1322,6 +1330,11 @@ vmmops_getcap(void *vcpui, int num, int *retval) *retval = 1; ret = 0; break; + case VM_CAP_BRK_EXIT: + case VM_CAP_SS_EXIT: + case VM_CAP_MASK_HWINTR: + *retval = (hypctx->setcaps & (1ul << num)) != 0; + break; default: break; } @@ -1332,6 +1345,68 @@ vmmops_getcap(void *vcpui, int num, int *retval) int vmmops_setcap(void *vcpui, int num, int val) { + struct hypctx *hypctx = vcpui; + int ret; + + ret = 0; - return (ENOENT); + switch (num) { + case VM_CAP_BRK_EXIT: + if ((val != 0) == (hypctx->setcaps & (1ul << num)) != 0) + break; + if (val != 0) + hypctx->mdcr_el2 |= MDCR_EL2_TDE; + else + hypctx->mdcr_el2 &= ~MDCR_EL2_TDE; + break; + case VM_CAP_SS_EXIT: + if ((val != 0) == (hypctx->setcaps & (1ul << num)) != 0) + break; + + if (val != 0) { + hypctx->debug_spsr |= (hypctx->tf.tf_spsr & PSR_SS); + hypctx->debug_mdscr |= hypctx->mdscr_el1 & + (MDSCR_SS | MDSCR_KDE); + + hypctx->tf.tf_spsr |= PSR_SS; + hypctx->mdscr_el1 |= MDSCR_SS | MDSCR_KDE; + hypctx->mdcr_el2 |= MDCR_EL2_TDE; + } else { + hypctx->tf.tf_spsr &= ~PSR_SS; + hypctx->tf.tf_spsr |= hypctx->debug_spsr; + hypctx->debug_spsr &= ~PSR_SS; + hypctx->mdscr_el1 &= ~(MDSCR_SS | MDSCR_KDE); + hypctx->mdscr_el1 |= hypctx->debug_mdscr; + hypctx->debug_mdscr &= ~(MDSCR_SS | MDSCR_KDE); + hypctx->mdcr_el2 &= ~MDCR_EL2_TDE; + } + break; + case VM_CAP_MASK_HWINTR: + if ((val != 0) == (hypctx->setcaps & (1ul << num)) != 0) + break; + + if (val != 0) { + hypctx->debug_spsr |= (hypctx->tf.tf_spsr & + (PSR_I | PSR_F)); + hypctx->tf.tf_spsr |= PSR_I | PSR_F; + } else { + hypctx->tf.tf_spsr &= ~(PSR_I | PSR_F); + hypctx->tf.tf_spsr |= (hypctx->debug_spsr & + (PSR_I | PSR_F)); + hypctx->debug_spsr &= ~(PSR_I | PSR_F); + } + break; + default: + ret = ENOENT; + break; + } + + if (ret == 0) { + if (val == 0) + hypctx->setcaps &= ~(1ul << num); + else + hypctx->setcaps |= (1ul << num); + } + + return (ret); } diff --git a/sys/arm64/vmm/vmm_stat.c b/sys/arm64/vmm/vmm_stat.c index 858ce980843a..05ece6f30579 100644 --- a/sys/arm64/vmm/vmm_stat.c +++ b/sys/arm64/vmm/vmm_stat.c @@ -161,5 +161,7 @@ VMM_STAT(VMEXIT_INSN_ABORT, "number of vmexits for an instruction abort"); VMM_STAT(VMEXIT_UNHANDLED_SYNC, "number of vmexits for an unhandled synchronous exception"); VMM_STAT(VMEXIT_IRQ, "number of vmexits for an irq"); VMM_STAT(VMEXIT_FIQ, "number of vmexits for an interrupt"); +VMM_STAT(VMEXIT_BRK, "number of vmexits for a breakpoint exception"); +VMM_STAT(VMEXIT_SS, "number of vmexits for a single-step exception"); VMM_STAT(VMEXIT_UNHANDLED_EL2, "number of vmexits for an unhandled EL2 exception"); VMM_STAT(VMEXIT_UNHANDLED, "number of vmexits for an unhandled exception"); diff --git a/sys/arm64/vmm/vmm_stat.h b/sys/arm64/vmm/vmm_stat.h index b0a06ef79253..402fa2f1b1e4 100644 --- a/sys/arm64/vmm/vmm_stat.h +++ b/sys/arm64/vmm/vmm_stat.h @@ -140,6 +140,8 @@ VMM_STAT_DECLARE(VMEXIT_INSN_ABORT); VMM_STAT_DECLARE(VMEXIT_UNHANDLED_SYNC); VMM_STAT_DECLARE(VMEXIT_IRQ); VMM_STAT_DECLARE(VMEXIT_FIQ); +VMM_STAT_DECLARE(VMEXIT_BRK); +VMM_STAT_DECLARE(VMEXIT_SS); VMM_STAT_DECLARE(VMEXIT_UNHANDLED_EL2); VMM_STAT_DECLARE(VMEXIT_UNHANDLED); #endif