mirror of https://gitlab.com/qemu-project/qemu
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
382 lines
12 KiB
C
382 lines
12 KiB
C
/* 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);
|
|
}
|
|
}
|
|
}
|
|
|