Browse Source
Arm leaves around some functions that use cpu_interrupt(), even for user-mode emulation when the code is unreachable. Pull out the system-mode implementation to a separate file, and add stubs for CONFIG_USER_ONLY. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>pull/305/head
6 changed files with 429 additions and 370 deletions
@ -0,0 +1,381 @@ |
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */ |
|||
|
|||
/*
|
|||
* QEMU ARM CPU - interrupt_request handling |
|||
* |
|||
* Copyright (c) 2003-2025 QEMU contributors |
|||
*/ |
|||
|
|||
#include "qemu/osdep.h" |
|||
#include "cpu.h" |
|||
#include "accel/tcg/cpu-ops.h" |
|||
#include "internals.h" |
|||
|
|||
#ifdef CONFIG_TCG |
|||
static inline bool arm_excp_unmasked(CPUState *cs, unsigned int excp_idx, |
|||
unsigned int target_el, |
|||
unsigned int cur_el, bool secure, |
|||
uint64_t hcr_el2) |
|||
{ |
|||
CPUARMState *env = cpu_env(cs); |
|||
bool pstate_unmasked; |
|||
bool unmasked = false; |
|||
bool allIntMask = false; |
|||
|
|||
/*
|
|||
* Don't take exceptions if they target a lower EL. |
|||
* This check should catch any exceptions that would not be taken |
|||
* but left pending. |
|||
*/ |
|||
if (cur_el > target_el) { |
|||
return false; |
|||
} |
|||
|
|||
if (cpu_isar_feature(aa64_nmi, env_archcpu(env)) && |
|||
env->cp15.sctlr_el[target_el] & SCTLR_NMI && cur_el == target_el) { |
|||
allIntMask = env->pstate & PSTATE_ALLINT || |
|||
((env->cp15.sctlr_el[target_el] & SCTLR_SPINTMASK) && |
|||
(env->pstate & PSTATE_SP)); |
|||
} |
|||
|
|||
switch (excp_idx) { |
|||
case EXCP_NMI: |
|||
pstate_unmasked = !allIntMask; |
|||
break; |
|||
|
|||
case EXCP_VINMI: |
|||
if (!(hcr_el2 & HCR_IMO) || (hcr_el2 & HCR_TGE)) { |
|||
/* VINMIs are only taken when hypervized. */ |
|||
return false; |
|||
} |
|||
return !allIntMask; |
|||
case EXCP_VFNMI: |
|||
if (!(hcr_el2 & HCR_FMO) || (hcr_el2 & HCR_TGE)) { |
|||
/* VFNMIs are only taken when hypervized. */ |
|||
return false; |
|||
} |
|||
return !allIntMask; |
|||
case EXCP_FIQ: |
|||
pstate_unmasked = (!(env->daif & PSTATE_F)) && (!allIntMask); |
|||
break; |
|||
|
|||
case EXCP_IRQ: |
|||
pstate_unmasked = (!(env->daif & PSTATE_I)) && (!allIntMask); |
|||
break; |
|||
|
|||
case EXCP_VFIQ: |
|||
if (!(hcr_el2 & HCR_FMO) || (hcr_el2 & HCR_TGE)) { |
|||
/* VFIQs are only taken when hypervized. */ |
|||
return false; |
|||
} |
|||
return !(env->daif & PSTATE_F) && (!allIntMask); |
|||
case EXCP_VIRQ: |
|||
if (!(hcr_el2 & HCR_IMO) || (hcr_el2 & HCR_TGE)) { |
|||
/* VIRQs are only taken when hypervized. */ |
|||
return false; |
|||
} |
|||
return !(env->daif & PSTATE_I) && (!allIntMask); |
|||
case EXCP_VSERR: |
|||
if (!(hcr_el2 & HCR_AMO) || (hcr_el2 & HCR_TGE)) { |
|||
/* VIRQs are only taken when hypervized. */ |
|||
return false; |
|||
} |
|||
return !(env->daif & PSTATE_A); |
|||
default: |
|||
g_assert_not_reached(); |
|||
} |
|||
|
|||
/*
|
|||
* Use the target EL, current execution state and SCR/HCR settings to |
|||
* determine whether the corresponding CPSR bit is used to mask the |
|||
* interrupt. |
|||
*/ |
|||
if ((target_el > cur_el) && (target_el != 1)) { |
|||
/* Exceptions targeting a higher EL may not be maskable */ |
|||
if (arm_feature(env, ARM_FEATURE_AARCH64)) { |
|||
switch (target_el) { |
|||
case 2: |
|||
/*
|
|||
* According to ARM DDI 0487H.a, an interrupt can be masked |
|||
* when HCR_E2H and HCR_TGE are both set regardless of the |
|||
* current Security state. Note that we need to revisit this |
|||
* part again once we need to support NMI. |
|||
*/ |
|||
if ((hcr_el2 & (HCR_E2H | HCR_TGE)) != (HCR_E2H | HCR_TGE)) { |
|||
unmasked = true; |
|||
} |
|||
break; |
|||
case 3: |
|||
/* Interrupt cannot be masked when the target EL is 3 */ |
|||
unmasked = true; |
|||
break; |
|||
default: |
|||
g_assert_not_reached(); |
|||
} |
|||
} else { |
|||
/*
|
|||
* The old 32-bit-only environment has a more complicated |
|||
* masking setup. HCR and SCR bits not only affect interrupt |
|||
* routing but also change the behaviour of masking. |
|||
*/ |
|||
bool hcr, scr; |
|||
|
|||
switch (excp_idx) { |
|||
case EXCP_FIQ: |
|||
/*
|
|||
* If FIQs are routed to EL3 or EL2 then there are cases where |
|||
* we override the CPSR.F in determining if the exception is |
|||
* masked or not. If neither of these are set then we fall back |
|||
* to the CPSR.F setting otherwise we further assess the state |
|||
* below. |
|||
*/ |
|||
hcr = hcr_el2 & HCR_FMO; |
|||
scr = (env->cp15.scr_el3 & SCR_FIQ); |
|||
|
|||
/*
|
|||
* When EL3 is 32-bit, the SCR.FW bit controls whether the |
|||
* CPSR.F bit masks FIQ interrupts when taken in non-secure |
|||
* state. If SCR.FW is set then FIQs can be masked by CPSR.F |
|||
* when non-secure but only when FIQs are only routed to EL3. |
|||
*/ |
|||
scr = scr && !((env->cp15.scr_el3 & SCR_FW) && !hcr); |
|||
break; |
|||
case EXCP_IRQ: |
|||
/*
|
|||
* When EL3 execution state is 32-bit, if HCR.IMO is set then |
|||
* we may override the CPSR.I masking when in non-secure state. |
|||
* The SCR.IRQ setting has already been taken into consideration |
|||
* when setting the target EL, so it does not have a further |
|||
* affect here. |
|||
*/ |
|||
hcr = hcr_el2 & HCR_IMO; |
|||
scr = false; |
|||
break; |
|||
default: |
|||
g_assert_not_reached(); |
|||
} |
|||
|
|||
if ((scr || hcr) && !secure) { |
|||
unmasked = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/*
|
|||
* The PSTATE bits only mask the interrupt if we have not overridden the |
|||
* ability above. |
|||
*/ |
|||
return unmasked || pstate_unmasked; |
|||
} |
|||
|
|||
bool arm_cpu_exec_interrupt(CPUState *cs, int interrupt_request) |
|||
{ |
|||
CPUARMState *env = cpu_env(cs); |
|||
uint32_t cur_el = arm_current_el(env); |
|||
bool secure = arm_is_secure(env); |
|||
uint64_t hcr_el2 = arm_hcr_el2_eff(env); |
|||
uint32_t target_el; |
|||
uint32_t excp_idx; |
|||
|
|||
/* The prioritization of interrupts is IMPLEMENTATION DEFINED. */ |
|||
|
|||
if (cpu_isar_feature(aa64_nmi, env_archcpu(env)) && |
|||
(arm_sctlr(env, cur_el) & SCTLR_NMI)) { |
|||
if (interrupt_request & CPU_INTERRUPT_NMI) { |
|||
excp_idx = EXCP_NMI; |
|||
target_el = arm_phys_excp_target_el(cs, excp_idx, cur_el, secure); |
|||
if (arm_excp_unmasked(cs, excp_idx, target_el, |
|||
cur_el, secure, hcr_el2)) { |
|||
goto found; |
|||
} |
|||
} |
|||
if (interrupt_request & CPU_INTERRUPT_VINMI) { |
|||
excp_idx = EXCP_VINMI; |
|||
target_el = 1; |
|||
if (arm_excp_unmasked(cs, excp_idx, target_el, |
|||
cur_el, secure, hcr_el2)) { |
|||
goto found; |
|||
} |
|||
} |
|||
if (interrupt_request & CPU_INTERRUPT_VFNMI) { |
|||
excp_idx = EXCP_VFNMI; |
|||
target_el = 1; |
|||
if (arm_excp_unmasked(cs, excp_idx, target_el, |
|||
cur_el, secure, hcr_el2)) { |
|||
goto found; |
|||
} |
|||
} |
|||
} else { |
|||
/*
|
|||
* NMI disabled: interrupts with superpriority are handled |
|||
* as if they didn't have it |
|||
*/ |
|||
if (interrupt_request & CPU_INTERRUPT_NMI) { |
|||
interrupt_request |= CPU_INTERRUPT_HARD; |
|||
} |
|||
if (interrupt_request & CPU_INTERRUPT_VINMI) { |
|||
interrupt_request |= CPU_INTERRUPT_VIRQ; |
|||
} |
|||
if (interrupt_request & CPU_INTERRUPT_VFNMI) { |
|||
interrupt_request |= CPU_INTERRUPT_VFIQ; |
|||
} |
|||
} |
|||
|
|||
if (interrupt_request & CPU_INTERRUPT_FIQ) { |
|||
excp_idx = EXCP_FIQ; |
|||
target_el = arm_phys_excp_target_el(cs, excp_idx, cur_el, secure); |
|||
if (arm_excp_unmasked(cs, excp_idx, target_el, |
|||
cur_el, secure, hcr_el2)) { |
|||
goto found; |
|||
} |
|||
} |
|||
if (interrupt_request & CPU_INTERRUPT_HARD) { |
|||
excp_idx = EXCP_IRQ; |
|||
target_el = arm_phys_excp_target_el(cs, excp_idx, cur_el, secure); |
|||
if (arm_excp_unmasked(cs, excp_idx, target_el, |
|||
cur_el, secure, hcr_el2)) { |
|||
goto found; |
|||
} |
|||
} |
|||
if (interrupt_request & CPU_INTERRUPT_VIRQ) { |
|||
excp_idx = EXCP_VIRQ; |
|||
target_el = 1; |
|||
if (arm_excp_unmasked(cs, excp_idx, target_el, |
|||
cur_el, secure, hcr_el2)) { |
|||
goto found; |
|||
} |
|||
} |
|||
if (interrupt_request & CPU_INTERRUPT_VFIQ) { |
|||
excp_idx = EXCP_VFIQ; |
|||
target_el = 1; |
|||
if (arm_excp_unmasked(cs, excp_idx, target_el, |
|||
cur_el, secure, hcr_el2)) { |
|||
goto found; |
|||
} |
|||
} |
|||
if (interrupt_request & CPU_INTERRUPT_VSERR) { |
|||
excp_idx = EXCP_VSERR; |
|||
target_el = 1; |
|||
if (arm_excp_unmasked(cs, excp_idx, target_el, |
|||
cur_el, secure, hcr_el2)) { |
|||
/* Taking a virtual abort clears HCR_EL2.VSE */ |
|||
env->cp15.hcr_el2 &= ~HCR_VSE; |
|||
cpu_reset_interrupt(cs, CPU_INTERRUPT_VSERR); |
|||
goto found; |
|||
} |
|||
} |
|||
return false; |
|||
|
|||
found: |
|||
cs->exception_index = excp_idx; |
|||
env->exception.target_el = target_el; |
|||
cs->cc->tcg_ops->do_interrupt(cs); |
|||
return true; |
|||
} |
|||
#endif /* CONFIG_TCG */ |
|||
|
|||
void arm_cpu_update_virq(ARMCPU *cpu) |
|||
{ |
|||
/*
|
|||
* Update the interrupt level for VIRQ, which is the logical OR of |
|||
* the HCR_EL2.VI bit and the input line level from the GIC. |
|||
*/ |
|||
CPUARMState *env = &cpu->env; |
|||
CPUState *cs = CPU(cpu); |
|||
|
|||
bool new_state = ((arm_hcr_el2_eff(env) & HCR_VI) && |
|||
!(arm_hcrx_el2_eff(env) & HCRX_VINMI)) || |
|||
(env->irq_line_state & CPU_INTERRUPT_VIRQ); |
|||
|
|||
if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VIRQ)) { |
|||
if (new_state) { |
|||
cpu_interrupt(cs, CPU_INTERRUPT_VIRQ); |
|||
} else { |
|||
cpu_reset_interrupt(cs, CPU_INTERRUPT_VIRQ); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void arm_cpu_update_vfiq(ARMCPU *cpu) |
|||
{ |
|||
/*
|
|||
* Update the interrupt level for VFIQ, which is the logical OR of |
|||
* the HCR_EL2.VF bit and the input line level from the GIC. |
|||
*/ |
|||
CPUARMState *env = &cpu->env; |
|||
CPUState *cs = CPU(cpu); |
|||
|
|||
bool new_state = ((arm_hcr_el2_eff(env) & HCR_VF) && |
|||
!(arm_hcrx_el2_eff(env) & HCRX_VFNMI)) || |
|||
(env->irq_line_state & CPU_INTERRUPT_VFIQ); |
|||
|
|||
if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VFIQ)) { |
|||
if (new_state) { |
|||
cpu_interrupt(cs, CPU_INTERRUPT_VFIQ); |
|||
} else { |
|||
cpu_reset_interrupt(cs, CPU_INTERRUPT_VFIQ); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void arm_cpu_update_vinmi(ARMCPU *cpu) |
|||
{ |
|||
/*
|
|||
* Update the interrupt level for VINMI, which is the logical OR of |
|||
* the HCRX_EL2.VINMI bit and the input line level from the GIC. |
|||
*/ |
|||
CPUARMState *env = &cpu->env; |
|||
CPUState *cs = CPU(cpu); |
|||
|
|||
bool new_state = ((arm_hcr_el2_eff(env) & HCR_VI) && |
|||
(arm_hcrx_el2_eff(env) & HCRX_VINMI)) || |
|||
(env->irq_line_state & CPU_INTERRUPT_VINMI); |
|||
|
|||
if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VINMI)) { |
|||
if (new_state) { |
|||
cpu_interrupt(cs, CPU_INTERRUPT_VINMI); |
|||
} else { |
|||
cpu_reset_interrupt(cs, CPU_INTERRUPT_VINMI); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void arm_cpu_update_vfnmi(ARMCPU *cpu) |
|||
{ |
|||
/*
|
|||
* Update the interrupt level for VFNMI, which is the HCRX_EL2.VFNMI bit. |
|||
*/ |
|||
CPUARMState *env = &cpu->env; |
|||
CPUState *cs = CPU(cpu); |
|||
|
|||
bool new_state = (arm_hcr_el2_eff(env) & HCR_VF) && |
|||
(arm_hcrx_el2_eff(env) & HCRX_VFNMI); |
|||
|
|||
if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VFNMI)) { |
|||
if (new_state) { |
|||
cpu_interrupt(cs, CPU_INTERRUPT_VFNMI); |
|||
} else { |
|||
cpu_reset_interrupt(cs, CPU_INTERRUPT_VFNMI); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void arm_cpu_update_vserr(ARMCPU *cpu) |
|||
{ |
|||
/*
|
|||
* Update the interrupt level for VSERR, which is the HCR_EL2.VSE bit. |
|||
*/ |
|||
CPUARMState *env = &cpu->env; |
|||
CPUState *cs = CPU(cpu); |
|||
|
|||
bool new_state = env->cp15.hcr_el2 & HCR_VSE; |
|||
|
|||
if (new_state != cpu_test_interrupt(cs, CPU_INTERRUPT_VSERR)) { |
|||
if (new_state) { |
|||
cpu_interrupt(cs, CPU_INTERRUPT_VSERR); |
|||
} else { |
|||
cpu_reset_interrupt(cs, CPU_INTERRUPT_VSERR); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,37 @@ |
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */ |
|||
|
|||
/* QEMU ARM CPU - user-mode emulation stubs for EL2 interrupts
|
|||
* |
|||
* These should not really be needed, but CP registers for EL2 |
|||
* are not elided by user-mode emulation and they call these |
|||
* functions. Leave them as stubs until it's cleaned up. |
|||
*/ |
|||
|
|||
#include "qemu/osdep.h" |
|||
#include "cpu.h" |
|||
#include "internals.h" |
|||
|
|||
void arm_cpu_update_virq(ARMCPU *cpu) |
|||
{ |
|||
g_assert_not_reached(); |
|||
} |
|||
|
|||
void arm_cpu_update_vfiq(ARMCPU *cpu) |
|||
{ |
|||
g_assert_not_reached(); |
|||
} |
|||
|
|||
void arm_cpu_update_vinmi(ARMCPU *cpu) |
|||
{ |
|||
g_assert_not_reached(); |
|||
} |
|||
|
|||
void arm_cpu_update_vfnmi(ARMCPU *cpu) |
|||
{ |
|||
g_assert_not_reached(); |
|||
} |
|||
|
|||
void arm_cpu_update_vserr(ARMCPU *cpu) |
|||
{ |
|||
g_assert_not_reached(); |
|||
} |
|||
Loading…
Reference in new issue