| /* 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); |
| } |
| } |
| } |
| |