Re: [PATCH net-next v7 15/17] net: macb: read ISR inside bp->lock critical section

Théo Lebrun <[email protected]>
Newsgroups org.kernel.vger.netdev,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Replying to sashiko
https://sashiko.dev/#/patchset/20260803-macb-context-v7-0-4d7d4af04849%40bootlin.com

> commit add26bbfae8660854cbc34d74da1557f7bfd9fae
> Author: Théo Lebrun <[email protected]>
>
> net: macb: read ISR inside bp->lock critical section
>
> The IRQ handler reads ISR register into the `status` stack variable.
> If empty, it early returns. Else, it grabs bp->lock and iterates on
> the status bits.
>
> We risk a race on spinlock acquire; status might have changed.
> Move the readl(ISR) inside the bp->lock critical section.
>
> In practice, we protect ourselves from interpreting the same ISR status
> twice. To understand issues, read macb_interrupt() body and imagine
> each codepath being performed twice:
>  - Two napi_schedule_irqoff() are idempotent *if fast enough*.
> >  - Two schedule_work(tx_error_task) are idempotent *if fast enough*.
> >  - Double increment of rx_overruns.
> >  - Two queue_work(hresp_err_bh_work) are idempotent *if fast enough*.
>
> [...]
>
> > diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
> > index 7e77c24da8a63..0778ce9b3223d 100644
> > --- a/drivers/net/ethernet/cadence/macb_main.c
> > +++ b/drivers/net/ethernet/cadence/macb_main.c
> > @@ -2193,12 +2193,13 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id)
> >  	bool hresp_err = false;
> >  	u32 status;
> >  
> > -	status = queue_readl(queue, ISR);
> > +	spin_lock(&bp->lock);
> >  
> > -	if (unlikely(!status))
> > +	status = queue_readl(queue, ISR);
>
> [...]
>
> > +	if (unlikely(!status)) {
> > +		spin_unlock(&bp->lock);
> >  		return IRQ_NONE;
> > -
> > -	spin_lock(&bp->lock);
> > +	}
>
> [Severity: High]
> Does unconditionally acquiring bp->lock in this shared IRQ handler before
> verifying the interrupt status introduce severe lock contention?
>
> Since macb_interrupt() is registered with IRQF_SHARED, it is called when
> another device on the same IRQ line triggers an interrupt.
>
> If macb_tx_error_task() is running on another CPU, it acquires bp->lock
> with IRQs disabled and polls hardware for up to 14ms (MACB_HALT_TIMEOUT):
>
> macb_tx_error_task() {
>     ...
>     spin_lock_irqsave(&bp->lock, flags);
>     netif_tx_stop_all_queues(bp->netdev);
>     if (macb_halt_tx(bp)) {
>     ...
> }
>
> During this time, wouldn't the CPU servicing the foreign interrupt spin on
> bp->lock with local interrupts disabled for up to 14ms, only to eventually
> read ISR == 0 and return IRQ_NONE? Could this stall other devices sharing
> the IRQ line?

So it's describing two separate issues here.
As usual, Sashiko isn't the best at summaries.

 - macb_interrupt() now unconditionally grabs bp->lock. That combined
   with it being a shared IRQ => when we land in the IRQ_NONE case we
   now have locked bp->lock for "no reason".

   => As described before, correctness > performance. Here we don't have
      the choice unless we fully redesign the driver.

   See this reply to Sashiko on previous revision:
   https://lore.kernel.org/all/[email protected]/

 - macb_tx_error_task() has a codepath where it blocks bp->lock for a
   14ms timeout.

   => Unrelated to this specific patch or even to the whole series.

Thanks,
-- 
Théo Lebrun, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.