On Tue, 3 Feb 2026 at 13:06, Ashish Anand <[email protected]> wrote:
>
> Currently, QEMU implements the 'Wait For Event' (WFE) instruction as a
> simple yield. This causes high host CPU usage because guest
> RTOS idle loops effectively become busy-wait loops.
>
> To improve efficiency, this patch transitions WFE to use the architectural
> 'Halt' state (EXCP_HLT) for M-profile CPUs. This allows the host thread
> to sleep when the guest is idle.
>
> To support this transition, we implement the full architectural behavior
> required for WFE, specifically the 'Event Register', 'SEVONPEND' logic,
> and 'R_BPBR' exception handling requirements defined in the ARM
> Architecture Reference Manual.
>
> This patch enables resource-efficient idle emulation for Cortex-M.
>
> Signed-off-by: Ashish Anand <[email protected]>
> ---
>
> Changes in v3:
> 1. Changed event_register from uint32_t to bool, as it's architecturally
>    a single-bit register. (Alex Bennée)
> 2. Refactored nvic_update_pending_state() to accept VecInfo* and scr_bank
>    as parameters instead of recalculating them from irq/secure. (Peter 
> Maydell)
>
> Link to v2 - 
> https://lore.kernel.org/qemu-devel/[email protected]
>
>
>  hw/intc/armv7m_nvic.c      | 114 ++++++++++++++++++++++++++++++-------
>  target/arm/cpu.c           |   6 ++
>  target/arm/cpu.h           |   7 +++
>  target/arm/machine.c       |  19 +++++++
>  target/arm/tcg/helper.h    |   1 +
>  target/arm/tcg/m_helper.c  |   5 ++
>  target/arm/tcg/op_helper.c |  56 +++++++++++++++---
>  target/arm/tcg/t16.decode  |   5 +-
>  target/arm/tcg/t32.decode  |   5 +-
>  target/arm/tcg/translate.c |  29 ++++++++--
>  10 files changed, 212 insertions(+), 35 deletions(-)
>
> diff --git a/hw/intc/armv7m_nvic.c b/hw/intc/armv7m_nvic.c
> index 28b34e9944..0be8facb8e 100644
> --- a/hw/intc/armv7m_nvic.c
> +++ b/hw/intc/armv7m_nvic.c
> @@ -221,6 +221,29 @@ static int exc_group_prio(NVICState *s, int rawprio, 
> bool targets_secure)
>      return rawprio;
>  }
>
> +/*
> + * Update the pending state of an exception vector.
> + * This is the central function for all updates to vec->pending.
> + * Handles SEVONPEND: if this is a 0->1 transition on an external interrupt
> + * and SEVONPEND is set in the appropriate SCR, sets the event register.
> + */
> +static void nvic_update_pending_state(NVICState *s, VecInfo *vec,
> +                                      int irq, int scr_bank,
> +                                      uint8_t next_pending_val)
> +{
> +    uint8_t prev_pending_val = vec->pending;
> +    vec->pending = next_pending_val;
> +
> +    /* Check for 0->1 transition on interrupts (>= NVIC_FIRST_IRQ) only */
> +    if (!prev_pending_val && next_pending_val && irq >= NVIC_FIRST_IRQ) {
> +        /* SEVONPEND: interrupt going to pending is a WFE wakeup event */
> +        if (s->cpu->env.v7m.scr[scr_bank] & R_V7M_SCR_SEVONPEND_MASK) {
> +            s->cpu->env.event_register = true;
> +            qemu_cpu_kick(CPU(s->cpu));
> +        }
> +    }

I looked a bit more closely at the SEVONPEND language in the Arm ARM,
and it says

# When SCR.SEVONPEND bit associated with a security state is one, interrupts
# transitioning from the inactive to the pending state that target that
# security state are wakeup events.

So the thing that determines whether we are looking at the S or the NS
SCR.SEVONPEND is "does this interrupt target S or NS security state?".
We have a function for that:
 exc_targets_secure(s, irq)

So we can determine which SCR to look at in this nvic_update_pending_state()
function and we don't need the callers to pass in scr_bank to us.

exc_targets_secure() requires it to be called only for a non-banked
exception, but we can call it only inside the irq >= NVIC_FIRST_IRQ
condition, as all interrupts are non-banked.

This should simplify the changes you're making at the callsites.

thanks
-- PMM

Reply via email to