adin1110_net_stop() programs the RX-ready interrupt mask of the wrong
port: it uses ADIN2111_RX_RDY_IRQ (the second port's bit) when stopping
port 0 and ADIN1110_RX_RDY_IRQ (the first port's bit) when stopping
port 1, while adin1110_port_rx_ready() uses the opposite mapping, port 0
-> ADIN1110_RX_RDY and port 1 -> ADIN2111_P2_RX_RDY.
The port being stopped therefore keeps its own RX interrupt enabled while
the peer's is masked. On ADIN2111 that stalls RX on the peer port; on
ADIN1110 the RX interrupt is never masked at all. As the interrupt is
level triggered and adin1110_read_frames() skips the FIFO while
netif_oper_up() is false, the stopped port also keeps re-raising its
interrupt with nobody draining the FIFO.
Swap the two bits so that the port being stopped masks its own interrupt.
adin1110_net_open() rewrites the whole IMASK1 register and, on ADIN2111,
always includes ADIN2111_RX_RDY_IRQ, so opening one port also re-enables
the RX-ready interrupt of a peer port that is still down and undoes the
mask just programmed by its stop. Only keep the peer port's bit enabled
while that port is running; the write stays absolute, so the bit is still
included when the peer is up.
A port's RX-ready IRQ is now enabled only while that port is open: the
peer's bit is never re-enabled for a stopped port. IMASK1 is written in
exactly two places, adin1110_net_open() and adin1110_net_stop(), both
under priv->lock, and both compute the mask from the state of the two
ports. priv->irq_mask keeps its previous value and meaning (the STATUS1
write-one-to-clear mask), so the status clearing is unchanged.
Single-port ADIN1110 is unchanged.
Found by code review while comparing the bit mapping against
adin1110_port_rx_ready(). I do not have access to ADIN1110 or ADIN2111
hardware, so this was only compile-tested (CONFIG_ADIN1110=m and =y, gcc,
W=1, full kernel builds), not exercised on a device.
Fixes: bc93e19d088b ("net: ethernet: adi: Add ADIN1110 support")
Signed-off-by: Yuehong Wu <redacted>
---
v4:
- Also keep a stopped peer port's RX-ready IRQ masked in
adin1110_net_open(), whose absolute IMASK1 write re-enabled the
interrupt of a port that is still down and undid the stop mask.
Reported by Sashiko (netdev bot) as an incomplete fix.
- Did not carry the Reviewed-by from v3, as adin1110_net_open() is
changed as well; a Tested-by from ADI on real hardware would be
welcome.
v3: no code change. Cc the ADI commit-signers that get_maintainer.pl
reports once a git history is available (Nuno Sa, Heiner Kallweit,
Chen Ni). Thanks to Andrew Lunn for pointing out that Nuno was missing.
v2:
- Cc the netdev maintainers, as flagged by cc_maintainers on v1.
- Explain how the issue was found and that it was only compile-tested.
v1: https://lore.kernel.org/all/20261103-adin1110-net-stop-1-yuehongwu@cmss.chinamobile.com/ (local)
drivers/net/ethernet/adi/adin1110.c | 25 +++++++++++++++++++------
1 file changed, 19 insertions(+), 6 deletions(-)
diff --git a/drivers/net/ethernet/adi/adin1110.c b/drivers/net/ethernet/adi/adin1110.c
--- a/drivers/net/ethernet/adi/adin1110.c
+++ b/drivers/net/ethernet/adi/adin1110.c
@@ -885,6 +885,7 @@
{
struct adin1110_port_priv *port_priv = netdev_priv(net_dev);
struct adin1110_priv *priv = port_priv->priv;
+ u32 common, own, peer, irq_mask;
u32 val;
int ret;
@@ -895,12 +896,24 @@
if (ret < 0)
goto out;
- val = ADIN1110_TX_RDY_IRQ | ADIN1110_RX_RDY_IRQ | ADIN1110_SPI_ERR_IRQ;
- if (priv->cfg->id == ADIN2111_MAC)
- val |= ADIN2111_RX_RDY_IRQ;
+ common = ADIN1110_TX_RDY_IRQ | ADIN1110_SPI_ERR_IRQ;
+ own = !port_priv->nr ? ADIN1110_RX_RDY_IRQ : ADIN2111_RX_RDY_IRQ;
+ peer = !port_priv->nr ? ADIN2111_RX_RDY_IRQ : ADIN1110_RX_RDY_IRQ;
+
+ /*
+ * The write below is absolute, so only keep the peer port's
+ * RX-ready IRQ enabled while that port is running; a stopped peer
+ * must stay masked until it is opened again.
+ */
+ irq_mask = common | own;
+ priv->irq_mask = common | ADIN1110_RX_RDY_IRQ;
+ if (priv->cfg->id == ADIN2111_MAC) {
+ priv->irq_mask |= ADIN2111_RX_RDY_IRQ;
+ if (netif_running(priv->ports[!port_priv->nr]->netdev))
+ irq_mask |= peer;
+ }
- priv->irq_mask = val;
- ret = adin1110_write_reg(priv, ADIN1110_IMASK1, ~val);
+ ret = adin1110_write_reg(priv, ADIN1110_IMASK1, ~irq_mask);
if (ret < 0) {
netdev_err(net_dev, "Failed to enable chip IRQs: %d\n", ret);
goto out;@@ -945,7 +958,7 @@
u32 mask;
int ret;
- mask = !port_priv->nr ? ADIN2111_RX_RDY_IRQ : ADIN1110_RX_RDY_IRQ;
+ mask = !port_priv->nr ? ADIN1110_RX_RDY_IRQ : ADIN2111_RX_RDY_IRQ;
/* Disable RX RDY IRQs */
mutex_lock(&priv->lock);