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
