On 2026/09/03 01:50 PM, Kirill A. Korinsky wrote:
> OpenBSD exposes this by raising CTPR while handling interrupts. If an
> edge remains pending when EOI lowers the servicing priority, QEMU
> reasserts INT without checking CTPR; IACK then returns the spurious
> vector and consumes the masked edge before CTPR permits delivery.
> 
> Signed-off-by: Kirill A. Korinsky <[email protected]>
> ---
>  hw/intc/openpic.c | 10 ++++++----
>  1 file changed, 6 insertions(+), 4 deletions(-)
> 
> diff --git a/hw/intc/openpic.c b/hw/intc/openpic.c
> index 99d2a1e65e..6f9a562163 100644
> --- a/hw/intc/openpic.c
> +++ b/hw/intc/openpic.c
> @@ -1033,8 +1033,9 @@ static void openpic_cpu_write_internal(void *opaque, 
> hwaddr addr,
>          n_IRQ = IRQ_get_next(opp, &dst->raised);
>          if (n_IRQ != -1) {
>              src = &opp->src[n_IRQ];
> -            if (s_IRQ == -1 ||
> -                IVPR_PRIORITY(src->ivpr) > dst->servicing.priority) {
> +            if (IVPR_PRIORITY(src->ivpr) > dst->ctpr &&
> +                (s_IRQ == -1 ||
> +                 IVPR_PRIORITY(src->ivpr) > dst->servicing.priority)) {

The original condition only gated the INT re-assertion on the servicing
priority, missing the CTPR dimension entirely.  The new guard mirrors exactly
what IRQ_local_pipe() already does at the assertion path (line 292), so the
three callsites are now consistent.

>                  DPRINTF("Raise OpenPIC INT output cpu %d irq %d",
>                          idx, n_IRQ);
>                  qemu_irq_raise(opp->dst[idx].irqs[OPENPIC_OUTPUT_INT]);
> @@ -1070,12 +1071,13 @@ static uint32_t openpic_iack(OpenPICState *opp, 
> IRQDest *dst, int cpu)
>      }
>  
>      src = &opp->src[irq];
> -    if (!(src->ivpr & IVPR_ACTIVITY_MASK) ||
> -            !(IVPR_PRIORITY(src->ivpr) > dst->ctpr)) {
> +    if (!(src->ivpr & IVPR_ACTIVITY_MASK)) {
>          error_report("%s: bad raised IRQ %d ctpr %d ivpr 0x%08x",
>                  __func__, irq, dst->ctpr, src->ivpr);
>          openpic_update_irq(opp, irq);
>          retval = opp->spve;
> +    } else if (IVPR_PRIORITY(src->ivpr) <= dst->ctpr) {
> +        return opp->spve;

Splitting the old combined condition is the right approach.  The !IVPR_ACTIVITY
and prio <= ctpr cases are semantically distinct:

- A missing ACTIVITY bit on a raised IRQ is a genuine inconsistency and deserves
  the existing error_report + openpic_update_irq treatment.
- A CTPR-masked IRQ in raised.queue is expected by design — openpic_update_irq
  intentionally keeps IRQs there even when masked - the below comment at
  hw/intc/openpic.c:282 mentions about it.  Treating it as a bug and calling
  openpic_update_irq on it was incorrect.

    /*
     * Even if the interrupt doesn't have enough priority,
     * it is still raised, in case ctpr is lowered later.
     */

The return opp->spve (early return) for the CTPR-masked case is also important —
it skips the edge-sensitive cleanup block that follows:

  if (!src->level) {
      src->ivpr &= ~IVPR_ACTIVITY_MASK;
      src->pending = 0;
      IRQ_resetbit(&dst->raised, irq);
  }

Without the early return, a CTPR-masked edge IRQ would have its activity bit
cleared and be removed from raised.queue — silently losing it before CTPR
permits delivery.

Note: I'm not an OpenPIC expert, so take this as a code-walkthrough review, but
based on my understanding the logic looks correct to me.

Reviewed-by: Amit Machhiwal <[email protected]>

Thanks,
Amit

Reply via email to