hv: Store exception info regs on entry, and use it

This is the second part of allowing nested exceptions

Signed-off-by: Hector Martin <marcan@marcan.st>
This commit is contained in:
Hector Martin 2021-09-21 21:09:23 +09:00
parent 607464f8b0
commit 25bc815821
4 changed files with 75 additions and 72 deletions

View file

@ -301,11 +301,11 @@ void hv_rearm(void)
msr(CNTP_CTL_EL0, CNTx_CTL_ENABLE); msr(CNTP_CTL_EL0, CNTx_CTL_ENABLE);
} }
void hv_check_rendezvous(u64 *regs) void hv_check_rendezvous(struct exc_info *ctx)
{ {
if (hv_want_cpu == smp_id()) { if (hv_want_cpu == smp_id()) {
hv_want_cpu = -1; hv_want_cpu = -1;
hv_exc_proxy(regs, START_HV, HV_USER_INTERRUPT, NULL); hv_exc_proxy(ctx, START_HV, HV_USER_INTERRUPT, NULL);
} else if (hv_want_cpu != -1) { } else if (hv_want_cpu != -1) {
// Unlock the HV so the target CPU can get into the proxy // Unlock the HV so the target CPU can get into the proxy
spin_unlock(&bhl); spin_unlock(&bhl);
@ -317,7 +317,7 @@ void hv_check_rendezvous(u64 *regs)
} }
} }
void hv_tick(u64 *regs) void hv_tick(struct exc_info *ctx)
{ {
if (hv_should_exit) { if (hv_should_exit) {
spin_unlock(&bhl); spin_unlock(&bhl);
@ -326,7 +326,7 @@ void hv_tick(u64 *regs)
hv_wdt_pet(); hv_wdt_pet();
iodev_handle_events(uartproxy_iodev); iodev_handle_events(uartproxy_iodev);
if (iodev_can_read(uartproxy_iodev)) { if (iodev_can_read(uartproxy_iodev)) {
hv_exc_proxy(regs, START_HV, HV_USER_INTERRUPT, NULL); hv_exc_proxy(ctx, START_HV, HV_USER_INTERRUPT, NULL);
} }
hv_vuart_poll(); hv_vuart_poll();
} }

View file

@ -3,6 +3,7 @@
#ifndef HV_H #ifndef HV_H
#define HV_H #define HV_H
#include "exception.h"
#include "iodev.h" #include "iodev.h"
#include "types.h" #include "types.h"
#include "uartproxy.h" #include "uartproxy.h"
@ -51,7 +52,7 @@ int hv_map_sw(u64 from, u64 to, u64 size);
int hv_map_hook(u64 from, hv_hook_t *hook, u64 size); int hv_map_hook(u64 from, hv_hook_t *hook, u64 size);
u64 hv_translate(u64 addr, bool s1only, bool w); u64 hv_translate(u64 addr, bool s1only, bool w);
u64 hv_pt_walk(u64 addr); u64 hv_pt_walk(u64 addr);
bool hv_handle_dabort(u64 *regs); bool hv_handle_dabort(struct exc_info *ctx);
bool hv_pa_write(u64 addr, u64 *val, int width); bool hv_pa_write(u64 addr, u64 *val, int width);
bool hv_pa_read(u64 addr, u64 *val, int width); bool hv_pa_read(u64 addr, u64 *val, int width);
bool hv_pa_rw(u64 addr, u64 *val, bool write, int width); bool hv_pa_rw(u64 addr, u64 *val, bool write, int width);
@ -64,7 +65,7 @@ void hv_vuart_poll(void);
void hv_map_vuart(u64 base, int irq, iodev_id_t iodev); void hv_map_vuart(u64 base, int irq, iodev_id_t iodev);
/* Exceptions */ /* Exceptions */
void hv_exc_proxy(u64 *regs, uartproxy_boot_reason_t reason, uartproxy_exc_code_t type, void hv_exc_proxy(struct exc_info *ctx, uartproxy_boot_reason_t reason, uartproxy_exc_code_t type,
void *extra); void *extra);
/* WDT */ /* WDT */
@ -94,7 +95,7 @@ void hv_rendezvous(void);
void hv_switch_cpu(int cpu); void hv_switch_cpu(int cpu);
void hv_arm_tick(void); void hv_arm_tick(void);
void hv_rearm(void); void hv_rearm(void);
void hv_check_rendezvous(u64 *regs); void hv_check_rendezvous(struct exc_info *ctx);
void hv_tick(u64 *regs); void hv_tick(struct exc_info *ctx);
#endif #endif

View file

@ -36,9 +36,10 @@ static u64 exc_entry_time;
extern u32 hv_cpus_in_guest; extern u32 hv_cpus_in_guest;
void hv_exc_proxy(u64 *regs, uartproxy_boot_reason_t reason, uartproxy_exc_code_t type, void *extra) void hv_exc_proxy(struct exc_info *ctx, uartproxy_boot_reason_t reason, uartproxy_exc_code_t type,
void *extra)
{ {
int from_el = FIELD_GET(SPSR_M, hv_get_spsr()) >> 2; int from_el = FIELD_GET(SPSR_M, ctx->spsr) >> 2;
hv_wdt_breadcrumb('P'); hv_wdt_breadcrumb('P');
@ -49,26 +50,15 @@ void hv_exc_proxy(u64 *regs, uartproxy_boot_reason_t reason, uartproxy_exc_code_
u64 entry_time = mrs(CNTPCT_EL0); u64 entry_time = mrs(CNTPCT_EL0);
#endif #endif
struct exc_info exc_info = { ctx->elr_phys = hv_translate(ctx->elr, false, false);
.cpu_id = smp_id(), ctx->far_phys = hv_translate(ctx->far, false, false);
.spsr = hv_get_spsr(), ctx->sp_phys = hv_translate(from_el == 0 ? ctx->sp[0] : ctx->sp[1], false, false);
.elr = hv_get_elr(), ctx->extra = extra;
.esr = hv_get_esr(),
.far = hv_get_far(),
.afsr1 = hv_get_afsr1(),
.sp = {mrs(SP_EL0), mrs(SP_EL1), 0},
.mpidr = mrs(MPIDR_EL1),
.elr_phys = hv_translate(hv_get_elr(), false, false),
.far_phys = hv_translate(hv_get_far(), false, false),
.sp_phys = hv_translate(from_el == 0 ? mrs(SP_EL0) : mrs(SP_EL1), false, false),
.extra = extra,
};
memcpy(exc_info.regs, regs, sizeof(exc_info.regs));
struct uartproxy_msg_start start = { struct uartproxy_msg_start start = {
.reason = reason, .reason = reason,
.code = type, .code = type,
.info = &exc_info, .info = ctx,
}; };
hv_wdt_suspend(); hv_wdt_suspend();
@ -77,11 +67,6 @@ void hv_exc_proxy(u64 *regs, uartproxy_boot_reason_t reason, uartproxy_exc_code_
switch (ret) { switch (ret) {
case EXC_RET_HANDLED: case EXC_RET_HANDLED:
memcpy(regs, exc_info.regs, sizeof(exc_info.regs));
hv_set_spsr(exc_info.spsr);
hv_set_elr(exc_info.elr);
msr(SP_EL0, exc_info.sp[0]);
msr(SP_EL1, exc_info.sp[1]);
hv_wdt_breadcrumb('p'); hv_wdt_breadcrumb('p');
#ifdef TIME_ACCOUNTING #ifdef TIME_ACCOUNTING
u64 lost = mrs(CNTPCT_EL0) - entry_time; u64 lost = mrs(CNTPCT_EL0) - entry_time;
@ -93,7 +78,7 @@ void hv_exc_proxy(u64 *regs, uartproxy_boot_reason_t reason, uartproxy_exc_code_
hv_exit_guest(); hv_exit_guest();
default: default:
printf("Guest exception not handled, rebooting.\n"); printf("Guest exception not handled, rebooting.\n");
print_regs(regs, 0); print_regs(ctx->regs, 0);
flush_and_reboot(); flush_and_reboot();
} }
} }
@ -144,13 +129,15 @@ static void hv_update_fiq(void)
_msr(sr_tkn(sr), regs[rt]); \ _msr(sr_tkn(sr), regs[rt]); \
return true; return true;
static bool hv_handle_msr(u64 *regs, u64 iss) static bool hv_handle_msr(struct exc_info *ctx, u64 iss)
{ {
u64 reg = iss & (ESR_ISS_MSR_OP0 | ESR_ISS_MSR_OP2 | ESR_ISS_MSR_OP1 | ESR_ISS_MSR_CRn | u64 reg = iss & (ESR_ISS_MSR_OP0 | ESR_ISS_MSR_OP2 | ESR_ISS_MSR_OP1 | ESR_ISS_MSR_CRn |
ESR_ISS_MSR_CRm); ESR_ISS_MSR_CRm);
u64 rt = FIELD_GET(ESR_ISS_MSR_Rt, iss); u64 rt = FIELD_GET(ESR_ISS_MSR_Rt, iss);
bool is_read = iss & ESR_ISS_MSR_DIR; bool is_read = iss & ESR_ISS_MSR_DIR;
u64 *regs = ctx->regs;
regs[31] = 0; regs[31] = 0;
switch (reg) { switch (reg) {
@ -244,9 +231,19 @@ static bool hv_handle_msr(u64 *regs, u64 iss)
return false; return false;
} }
static void hv_exc_entry(u64 *regs) static void hv_exc_entry(struct exc_info *ctx)
{ {
UNUSED(regs); ctx->spsr = hv_get_spsr();
ctx->elr = hv_get_elr();
ctx->esr = hv_get_esr();
ctx->far = hv_get_far();
ctx->afsr1 = hv_get_afsr1();
ctx->sp[0] = mrs(SP_EL0);
ctx->sp[1] = mrs(SP_EL1);
ctx->sp[2] = (u64)ctx;
ctx->cpu_id = smp_id();
ctx->mpidr = mrs(MPIDR_EL1);
__atomic_sub_fetch(&hv_cpus_in_guest, 1, __ATOMIC_ACQUIRE); __atomic_sub_fetch(&hv_cpus_in_guest, 1, __ATOMIC_ACQUIRE);
spin_lock(&bhl); spin_lock(&bhl);
hv_wdt_breadcrumb('X'); hv_wdt_breadcrumb('X');
@ -257,9 +254,8 @@ static void hv_exc_entry(u64 *regs)
msr(SYS_IMP_APL_PMCR0, pmcr0 & ~PMCR0_CNT_MASK); msr(SYS_IMP_APL_PMCR0, pmcr0 & ~PMCR0_CNT_MASK);
} }
static void hv_exc_exit(u64 *regs) static void hv_exc_exit(struct exc_info *ctx)
{ {
UNUSED(regs);
hv_wdt_breadcrumb('x'); hv_wdt_breadcrumb('x');
hv_update_fiq(); hv_update_fiq();
/* reenable PMU counters */ /* reenable PMU counters */
@ -267,30 +263,34 @@ static void hv_exc_exit(u64 *regs)
msr(CNTVOFF_EL2, stolen_time); msr(CNTVOFF_EL2, stolen_time);
spin_unlock(&bhl); spin_unlock(&bhl);
__atomic_add_fetch(&hv_cpus_in_guest, 1, __ATOMIC_ACQUIRE); __atomic_add_fetch(&hv_cpus_in_guest, 1, __ATOMIC_ACQUIRE);
hv_set_spsr(ctx->spsr);
hv_set_elr(ctx->elr);
msr(SP_EL0, ctx->sp[0]);
msr(SP_EL1, ctx->sp[1]);
} }
void hv_exc_sync(u64 *regs) void hv_exc_sync(struct exc_info *ctx)
{ {
hv_wdt_breadcrumb('S'); hv_wdt_breadcrumb('S');
hv_exc_entry(regs); hv_exc_entry(ctx);
bool handled = false; bool handled = false;
u64 esr = hv_get_esr(); u32 ec = FIELD_GET(ESR_EC, ctx->esr);
u32 ec = FIELD_GET(ESR_EC, esr);
switch (ec) { switch (ec) {
case ESR_EC_DABORT_LOWER: case ESR_EC_DABORT_LOWER:
hv_wdt_breadcrumb('D'); hv_wdt_breadcrumb('D');
handled = hv_handle_dabort(regs); handled = hv_handle_dabort(ctx);
break; break;
case ESR_EC_MSR: case ESR_EC_MSR:
hv_wdt_breadcrumb('M'); hv_wdt_breadcrumb('M');
handled = hv_handle_msr(regs, FIELD_GET(ESR_ISS, esr)); handled = hv_handle_msr(ctx, FIELD_GET(ESR_ISS, ctx->esr));
break; break;
case ESR_EC_IMPDEF: case ESR_EC_IMPDEF:
hv_wdt_breadcrumb('A'); hv_wdt_breadcrumb('A');
switch (FIELD_GET(ESR_ISS, esr)) { switch (FIELD_GET(ESR_ISS, ctx->esr)) {
case ESR_ISS_IMPDEF_MSR: case ESR_ISS_IMPDEF_MSR:
handled = hv_handle_msr(regs, hv_get_afsr1()); handled = hv_handle_msr(ctx, ctx->afsr1);
break; break;
} }
break; break;
@ -298,38 +298,38 @@ void hv_exc_sync(u64 *regs)
if (handled) { if (handled) {
hv_wdt_breadcrumb('+'); hv_wdt_breadcrumb('+');
hv_set_elr(hv_get_elr() + 4); ctx->elr += 4;
} else { } else {
hv_wdt_breadcrumb('-'); hv_wdt_breadcrumb('-');
hv_exc_proxy(regs, START_EXCEPTION_LOWER, EXC_SYNC, NULL); hv_exc_proxy(ctx, START_EXCEPTION_LOWER, EXC_SYNC, NULL);
} }
hv_exc_exit(regs); hv_exc_exit(ctx);
hv_wdt_breadcrumb('s'); hv_wdt_breadcrumb('s');
} }
void hv_exc_irq(u64 *regs) void hv_exc_irq(struct exc_info *ctx)
{ {
hv_wdt_breadcrumb('I'); hv_wdt_breadcrumb('I');
hv_exc_entry(regs); hv_exc_entry(ctx);
hv_exc_proxy(regs, START_EXCEPTION_LOWER, EXC_IRQ, NULL); hv_exc_proxy(ctx, START_EXCEPTION_LOWER, EXC_IRQ, NULL);
hv_exc_exit(regs); hv_exc_exit(ctx);
hv_wdt_breadcrumb('i'); hv_wdt_breadcrumb('i');
} }
void hv_exc_fiq(u64 *regs) void hv_exc_fiq(struct exc_info *ctx)
{ {
hv_wdt_breadcrumb('F'); hv_wdt_breadcrumb('F');
hv_exc_entry(regs); hv_exc_entry(ctx);
if (mrs(CNTP_CTL_EL0) == (CNTx_CTL_ISTATUS | CNTx_CTL_ENABLE)) { if (mrs(CNTP_CTL_EL0) == (CNTx_CTL_ISTATUS | CNTx_CTL_ENABLE)) {
msr(CNTP_CTL_EL0, CNTx_CTL_ISTATUS | CNTx_CTL_IMASK | CNTx_CTL_ENABLE); msr(CNTP_CTL_EL0, CNTx_CTL_ISTATUS | CNTx_CTL_IMASK | CNTx_CTL_ENABLE);
hv_tick(regs); hv_tick(ctx);
hv_arm_tick(); hv_arm_tick();
} }
if (mrs(CNTV_CTL_EL0) == (CNTx_CTL_ISTATUS | CNTx_CTL_ENABLE)) { if (mrs(CNTV_CTL_EL0) == (CNTx_CTL_ISTATUS | CNTx_CTL_ENABLE)) {
msr(CNTV_CTL_EL0, CNTx_CTL_ISTATUS | CNTx_CTL_IMASK | CNTx_CTL_ENABLE); msr(CNTV_CTL_EL0, CNTx_CTL_ISTATUS | CNTx_CTL_IMASK | CNTx_CTL_ENABLE);
hv_exc_proxy(regs, START_HV, HV_VTIMER, NULL); hv_exc_proxy(ctx, START_HV, HV_VTIMER, NULL);
} }
u64 reg = mrs(SYS_IMP_APL_PMCR0); u64 reg = mrs(SYS_IMP_APL_PMCR0);
@ -345,7 +345,7 @@ void hv_exc_fiq(u64 *regs)
if ((reg & UPMCR0_IMODE_MASK) == UPMCR0_IMODE_FIQ && (mrs(SYS_IMP_APL_UPMSR) & UPMSR_IACT)) { if ((reg & UPMCR0_IMODE_MASK) == UPMCR0_IMODE_FIQ && (mrs(SYS_IMP_APL_UPMSR) & UPMSR_IACT)) {
printf("[FIQ] UPMC IRQ, masking"); printf("[FIQ] UPMC IRQ, masking");
reg_clr(SYS_IMP_APL_UPMCR0, UPMCR0_IMODE_MASK); reg_clr(SYS_IMP_APL_UPMCR0, UPMCR0_IMODE_MASK);
hv_exc_proxy(regs, START_EXCEPTION_LOWER, EXC_FIQ, NULL); hv_exc_proxy(ctx, START_EXCEPTION_LOWER, EXC_FIQ, NULL);
} }
if (mrs(SYS_IMP_APL_IPI_SR_EL1) & IPI_SR_PENDING) { if (mrs(SYS_IMP_APL_IPI_SR_EL1) & IPI_SR_PENDING) {
@ -356,18 +356,18 @@ void hv_exc_fiq(u64 *regs)
msr(SYS_IMP_APL_IPI_SR_EL1, IPI_SR_PENDING); msr(SYS_IMP_APL_IPI_SR_EL1, IPI_SR_PENDING);
sysop("isb"); sysop("isb");
} }
hv_check_rendezvous(regs); hv_check_rendezvous(ctx);
// Handles guest timers // Handles guest timers
hv_exc_exit(regs); hv_exc_exit(ctx);
hv_wdt_breadcrumb('f'); hv_wdt_breadcrumb('f');
} }
void hv_exc_serr(u64 *regs) void hv_exc_serr(struct exc_info *ctx)
{ {
hv_wdt_breadcrumb('E'); hv_wdt_breadcrumb('E');
hv_exc_entry(regs); hv_exc_entry(ctx);
hv_exc_proxy(regs, START_EXCEPTION_LOWER, EXC_SERROR, NULL); hv_exc_proxy(ctx, START_EXCEPTION_LOWER, EXC_SERROR, NULL);
hv_exc_exit(regs); hv_exc_exit(ctx);
hv_wdt_breadcrumb('e'); hv_wdt_breadcrumb('e');
} }

View file

@ -443,11 +443,12 @@ union simd_reg {
u8 b[16]; u8 b[16];
}; };
static bool emulate_load(u64 *regs, u32 insn, u64 *val, u64 *width) static bool emulate_load(struct exc_info *ctx, u32 insn, u64 *val, u64 *width)
{ {
u64 Rt = insn & 0x1f; u64 Rt = insn & 0x1f;
u64 Rn = (insn >> 5) & 0x1f; u64 Rn = (insn >> 5) & 0x1f;
u64 imm9 = EXT((insn >> 12) & 0x1ff, 9); u64 imm9 = EXT((insn >> 12) & 0x1ff, 9);
u64 *regs = ctx->regs;
union simd_reg simd[32]; union simd_reg simd[32];
@ -571,11 +572,12 @@ static bool emulate_load(u64 *regs, u32 insn, u64 *val, u64 *width)
return true; return true;
} }
static bool emulate_store(u64 *regs, u32 insn, u64 *val, u64 *width) static bool emulate_store(struct exc_info *ctx, u32 insn, u64 *val, u64 *width)
{ {
u64 Rt = insn & 0x1f; u64 Rt = insn & 0x1f;
u64 Rn = (insn >> 5) & 0x1f; u64 Rn = (insn >> 5) & 0x1f;
u64 imm9 = EXT((insn >> 12) & 0x1ff, 9); u64 imm9 = EXT((insn >> 12) & 0x1ff, 9);
u64 *regs = ctx->regs;
*width = insn >> 30; *width = insn >> 30;
@ -701,7 +703,7 @@ bool hv_pa_rw(u64 addr, u64 *val, bool write, int width)
return hv_pa_read(addr, val, width); return hv_pa_read(addr, val, width);
} }
bool hv_handle_dabort(u64 *regs) bool hv_handle_dabort(struct exc_info *ctx)
{ {
hv_wdt_breadcrumb('0'); hv_wdt_breadcrumb('0');
u64 esr = hv_get_esr(); u64 esr = hv_get_esr();
@ -740,7 +742,7 @@ bool hv_handle_dabort(u64 *regs)
u64 target = pte & PTE_TARGET_MASK_L4; u64 target = pte & PTE_TARGET_MASK_L4;
u64 paddr = target | (far & MASK(VADDR_L4_OFFSET_BITS)); u64 paddr = target | (far & MASK(VADDR_L4_OFFSET_BITS));
u64 elr = hv_get_elr(); u64 elr = ctx->elr;
u64 elr_pa = hv_translate(elr, false, false); u64 elr_pa = hv_translate(elr, false, false);
if (!elr_pa) { if (!elr_pa) {
printf("HV: Failed to fetch instruction for data abort at 0x%lx\n", elr); printf("HV: Failed to fetch instruction for data abort at 0x%lx\n", elr);
@ -756,7 +758,7 @@ bool hv_handle_dabort(u64 *regs)
if (esr & ESR_ISS_DABORT_WnR) { if (esr & ESR_ISS_DABORT_WnR) {
hv_wdt_breadcrumb('W'); hv_wdt_breadcrumb('W');
if (!emulate_store(regs, insn, val, &width)) { if (!emulate_store(ctx, insn, val, &width)) {
printf("HV: store not emulated: 0x%08x at 0x%lx\n", insn, ipa); printf("HV: store not emulated: 0x%08x at 0x%lx\n", insn, ipa);
return false; return false;
} }
@ -797,7 +799,7 @@ bool hv_handle_dabort(u64 *regs)
.addr = ipa, .addr = ipa,
.data = {val[0], val[1]}, .data = {val[0], val[1]},
}; };
hv_exc_proxy(regs, START_HV, HV_HOOK_VM, &hook); hv_exc_proxy(ctx, START_HV, HV_HOOK_VM, &hook);
break; break;
} }
default: default:
@ -807,7 +809,7 @@ bool hv_handle_dabort(u64 *regs)
} else { } else {
hv_wdt_breadcrumb('R'); hv_wdt_breadcrumb('R');
if (!emulate_load(regs, insn, NULL, &width)) { if (!emulate_load(ctx, insn, NULL, &width)) {
printf("HV: load not emulated: 0x%08x at 0x%lx\n", insn, ipa); printf("HV: load not emulated: 0x%08x at 0x%lx\n", insn, ipa);
return false; return false;
} }
@ -841,7 +843,7 @@ bool hv_handle_dabort(u64 *regs)
.id = FIELD_GET(PTE_TARGET_MASK_L4, pte), .id = FIELD_GET(PTE_TARGET_MASK_L4, pte),
.addr = ipa, .addr = ipa,
}; };
hv_exc_proxy(regs, START_HV, HV_HOOK_VM, &hook); hv_exc_proxy(ctx, START_HV, HV_HOOK_VM, &hook);
memcpy(val, hook.data, sizeof(val)); memcpy(val, hook.data, sizeof(val));
break; break;
} }
@ -855,7 +857,7 @@ bool hv_handle_dabort(u64 *regs)
emit_mmiotrace(elr, ipa, val, width, 0, pte & SPTE_TRACE_UNBUF); emit_mmiotrace(elr, ipa, val, width, 0, pte & SPTE_TRACE_UNBUF);
hv_wdt_breadcrumb('8'); hv_wdt_breadcrumb('8');
if (!emulate_load(regs, insn, val, &width)) if (!emulate_load(ctx, insn, val, &width))
return false; return false;
hv_wdt_breadcrumb('9'); hv_wdt_breadcrumb('9');