The software free queue interrupt remains registered while both netdevs are down. Merely masking the device source does not stop a threaded handler which was already woken. That handler can refill the queue while resize frees its ring and page metadata, or re-enable the source after teardown. The free queue interrupt is routed through port 1, but both port IRQs register the global handler. Ignore the source on port 0, then disable the port 1 Linux IRQ for the entire resize. disable_irq() drains hard and threaded handlers and prevents a new thread from being woken. After the drain, mask the device source and rebuild the queue. If setup succeeds, re-enable the source before the Linux IRQ. On failure, keep the source masked, clear freed queue state and restore the Linux IRQ so a later open retries setup safely. Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet") Assisted-by: LLM Signed-off-by: Linus Walleij --- drivers/net/ethernet/cortina/gemini.c | 53 +++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c index 79c046364292..003108270d06 100644 --- a/drivers/net/ethernet/cortina/gemini.c +++ b/drivers/net/ethernet/cortina/gemini.c @@ -986,6 +986,8 @@ static int geth_setup_freeq(struct gemini_ethernet *geth) } kfree(geth->freeq_pages); + geth->freeq_pages = NULL; + geth->num_freeq_pages = 0; err_freeq: dma_free_coherent(geth->dev, sizeof(*geth->freeq_ring) << geth->freeq_order, @@ -1026,20 +1028,37 @@ static void geth_cleanup_freeq(struct gemini_ethernet *geth) } kfree(geth->freeq_pages); + geth->freeq_pages = NULL; + geth->num_freeq_pages = 0; dma_free_coherent(geth->dev, sizeof(*geth->freeq_ring) << geth->freeq_order, geth->freeq_ring, geth->freeq_dma_base); + geth->freeq_ring = NULL; +} + +static void geth_set_freeq_irq(struct gemini_ethernet *geth, bool enable) +{ + unsigned long flags; + u32 val; + + spin_lock_irqsave(&geth->irq_lock, flags); + val = readl(geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG); + if (enable) + val |= SWFQ_EMPTY_INT_BIT; + else + val &= ~SWFQ_EMPTY_INT_BIT; + writel(val, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG); + spin_unlock_irqrestore(&geth->irq_lock, flags); } /** * geth_resize_freeq() - resize the software queue depth * @port: the port requesting the change * - * This gets called at least once during probe() so the device queue gets - * "resized" from the hardware defaults. Since both ports/net devices share - * the same hardware queue, some synchronization between the ports is - * needed. + * This is called when a port is opened or its RX ring size changes. Since + * both ports/net devices share the same hardware queue, some synchronization + * between the ports is needed. */ static int geth_resize_freeq(struct gemini_ethernet_port *port) { @@ -1049,8 +1068,6 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port) struct net_device *other_netdev; unsigned int new_size = 0; unsigned int new_order; - unsigned long flags; - u32 en; int ret; ASSERT_RTNL(); @@ -1087,13 +1104,8 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port) if (geth->freeq_ring && geth->freeq_order == new_order) return 0; - spin_lock_irqsave(&geth->irq_lock, flags); - - /* Disable the software queue IRQs */ - en = readl(geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG); - en &= ~SWFQ_EMPTY_INT_BIT; - writel(en, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG); - spin_unlock_irqrestore(&geth->irq_lock, flags); + disable_irq(geth->port1->irq); + geth_set_freeq_irq(geth, false); /* Drop the old queue */ if (geth->freeq_ring) @@ -1103,14 +1115,10 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port) geth->freeq_order = new_order; ret = geth_setup_freeq(geth); - /* Restart the interrupts - NOTE if this is the first resize - * after probe(), this is where the interrupts get turned on - * in the first place. - */ - spin_lock_irqsave(&geth->irq_lock, flags); - en |= SWFQ_EMPTY_INT_BIT; - writel(en, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG); - spin_unlock_irqrestore(&geth->irq_lock, flags); + /* Enable the queue interrupt only after successful setup. */ + if (!ret) + geth_set_freeq_irq(geth, true); + enable_irq(geth->port1->irq); return ret; } @@ -2373,6 +2381,9 @@ static irqreturn_t gemini_port_irq(int irq, void *data) irqreturn_t ret = IRQ_NONE; u32 val, en; + if (!port->id) + return IRQ_NONE; + geth = port->geth; spin_lock(&geth->irq_lock); -- 2.56.0