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. Disable that 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 <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 40 +++++++++++++++++++++++------------
1 file changed, 26 insertions(+), 14 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index 15ad9f721dd5..a2daf22e7698 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -984,6 +984,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,
@@ -1024,10 +1026,28 @@ 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);
}
/**@@ -1047,8 +1067,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;
/* The software free queue interrupt is routed through port 1. */
@@ -1080,16 +1098,11 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
new_order = min(15, ilog2(new_size - 1) + 1);
dev_dbg(geth->dev, "set shared queue to size %d order %d\n",
new_size, new_order);
- if (geth->freeq_order == new_order)
+ 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,10 +1116,9 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port)
* 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);
+ if (!ret)
+ geth_set_freeq_irq(geth, true);
+ enable_irq(geth->port1->irq);
return ret;
}
--
2.55.0