Hi Zoltan,

nmi_pending captures the edge transition, while pending_level == 7 reflects
the current level. Level 7 interrupts are edge-triggered — only the
transition from a level less than 7 to level 7 initiates an NMI. If we just
tested pending_level == 7, then it would act as level-triggered (the SR.I <
pending_level check passes on every re-entry from rte as long as Level 7
stays asserted), which violates the spec.

Concretely, nmi_pending:
- Is set true only on the rising edge: level == 7 && pending_level != 7
- Is cleared after the NMI is taken: nmi_pending = false
- Is not re-set if Level 7 remains continuously asserted
This matters on the Sun-3 where the PROM routes the Intersil 7170 clock to
Level 7 (intreg=0x81). The IRQC re-evaluates on every clock tick, keeping
pending_level == 7. Without the separate edge flag, the CPU would take a
spurious NMI on every interrupt check instead of once per tick.

Thanks,
Dan.

On Tue, Aug 18, 2026 at 3:56 AM BALATON Zoltan <[email protected]> wrote:

> On Mon, 17 Aug 2026, 54weasels wrote:
> > High level description:
> > Level 7 interrupts are Non-Maskable Interrupts (NMI) on the M68k
> architecture. The hardware asserts an NMI only on the rising edge of the
> level 7 signal. The current QEMU implementation treats level 7 like a
> standard level interrupt. This patch ensures proper NMI edge-triggered
> semantics for level 7, which is strictly required by the Sun-3
> keyboard/mouse NMI routing logic.
> >
> > Impact on existing functionality:
> > Corrects NMI edge-triggering for all M68k boards, adhering closely to
> the Motorola specifications. Existing boards will now correctly require an
> edge transition to trigger consecutive NMIs.
> >
> > Context: This patch was originally submitted as part of the monolithic
> Sun-3 Machine Emulation series (
> https://patchew.org/QEMU/[email protected]/) and
> has been split into atomic components.
> > ---
> > target/m68k/cpu.h       |  1 +
> > target/m68k/helper.c    |  6 ++++++
> > target/m68k/op_helper.c | 14 ++++++--------
> > 3 files changed, 13 insertions(+), 8 deletions(-)
> >
> > diff --git a/target/m68k/cpu.h b/target/m68k/cpu.h
> > index 7cf3791108..058777b891 100644
> > --- a/target/m68k/cpu.h
> > +++ b/target/m68k/cpu.h
> > @@ -148,6 +148,7 @@ typedef struct CPUArchState {
> >
> >     int pending_vector;
> >     int pending_level;
> > +    bool nmi_pending;
>
> What's the difference between nmi_pending and pending_level == 7? If
> nothing do we need a new variable for it or could just test pending_level?
>
> Regards,
> BALATON Zoltan
>
> >     /* Fields up to this point are cleared by a CPU reset */
> >     struct {} end_reset_fields;
> > diff --git a/target/m68k/helper.c b/target/m68k/helper.c
> > index 68f523ea84..93739ccda7 100644
> > --- a/target/m68k/helper.c
> > +++ b/target/m68k/helper.c
> > @@ -949,6 +949,12 @@ void m68k_set_irq_level(M68kCPU *cpu, int level,
> uint8_t vector)
> >     CPUState *cs = CPU(cpu);
> >     CPUM68KState *env = &cpu->env;
> >
> > +    if (level == 7 && env->pending_level != 7) {
> > +        env->nmi_pending = true;
> > +    } else if (level != 7) {
> > +        env->nmi_pending = false;
> > +    }
> > +
> >     env->pending_level = level;
> >     env->pending_vector = vector;
> >     if (level) {
> > diff --git a/target/m68k/op_helper.c b/target/m68k/op_helper.c
> > index 38f7a68981..30af4a2631 100644
> > --- a/target/m68k/op_helper.c
> > +++ b/target/m68k/op_helper.c
> > @@ -522,14 +522,12 @@ bool m68k_cpu_exec_interrupt(CPUState *cs, int
> interrupt_request)
> > {
> >     CPUM68KState *env = cpu_env(cs);
> >
> > -    if (interrupt_request & CPU_INTERRUPT_HARD
> > -        && ((env->sr & SR_I) >> SR_I_SHIFT) < env->pending_level) {
> > -        /*
> > -         * Real hardware gets the interrupt vector via an IACK cycle
> > -         * at this point.  Current emulated hardware doesn't rely on
> > -         * this, so we provide/save the vector when the interrupt is
> > -         * first signalled.
> > -         */
> > +    if (env->nmi_pending) {
> > +        env->nmi_pending = false;
> > +        cs->exception_index = env->pending_vector;
> > +        do_interrupt_m68k_hardirq(env);
> > +        return true;
> > +    } else if (((env->sr & SR_I) >> SR_I_SHIFT) < env->pending_level) {
> >         cs->exception_index = env->pending_vector;
> >         do_interrupt_m68k_hardirq(env);
> >         return true;
> >
>

Reply via email to