[PATCH net-next v4 02/15] net: ethernet: cortina: Keep PHY-less port bound for shared IRQ
flat view
WARM2d
From: Linus Walleij <linusw@kernel.org>
Date: 2026-10-11 18:06:25
Subsystem:
arm/cortina systems gemini arm architecture, networking drivers, the rest · Maintainers:
Hans Ulli Kroll, Linus Walleij, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Revision v4 of 3 in this series.
Revisions (3)
- v2 [diff vs current]
- v3 [diff vs current]
- v4 current
The software free queue interrupt is routed through port 1 even on
boards which only connect a PHY to port 0. Since PHY setup was moved
into probe, the PHY-less port fails to bind and releases the threaded
handler needed by the shared queue.
Keep children without a PHY description bound as IRQ-only ports.
Publish a port to the parent only after its IRQ and optional netdev are
ready, and protect port lifetime and initialization state with RTNL.
Refuse shared queue setup until both ports have completed
initialization.
Fixes: 3e813d61401a ("net: gemini: Clean up phy registration")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 88 ++++++++++++++++++++++++++++-------
1 file changed, 70 insertions(+), 18 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index bf7652b04f1b..ec2e47688337 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c@@ -25,6 +25,7 @@ #include <linux/interrupt.h> #include <linux/reset.h> #include <linux/clk.h> +#include <linux/rtnetlink.h> #include <linux/of.h> #include <linux/of_mdio.h> #include <linux/of_net.h>
@@ -155,6 +156,7 @@ struct gemini_ethernet_port { struct gemini_ethernet { struct device *dev; void __iomem *base; + /* Protected by RTNL. */ struct gemini_ethernet_port *port0; struct gemini_ethernet_port *port1; bool initialized;
@@ -1051,10 +1053,17 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port) u32 en; int ret; + ASSERT_RTNL(); + + /* The software free queue interrupt is routed through port 1. */ + if (!geth->initialized || !geth->port0 || !geth->port1) + return -ENODEV; + if (netdev->dev_id == 0) - other_netdev = geth->port1->netdev; + other_port = geth->port1; else - other_netdev = geth->port0->netdev; + other_port = geth->port0; + other_netdev = other_port ? other_port->netdev : NULL; if (other_netdev && netif_running(other_netdev)) return -EBUSY;
@@ -1065,7 +1074,6 @@ static int geth_resize_freeq(struct gemini_ethernet_port *port) new_size, port->rxq_order); if (other_netdev) { - other_port = netdev_priv(other_netdev); new_size += 1 << (other_port->rxq_order + 1); netdev_dbg(other_netdev, "port %d size: %d order %d\n", other_netdev->dev_id,
@@ -2387,8 +2395,28 @@ static irqreturn_t gemini_port_irq(int irq, void *data) return ret; } +static void gemini_port_clear(struct gemini_ethernet_port *port) +{ + struct gemini_ethernet *geth = port->geth; + + ASSERT_RTNL(); + + if (!port->id && geth->port0 == port) + geth->port0 = NULL; + else if (port->id && geth->port1 == port) + geth->port1 = NULL; + else + return; + + geth->initialized = false; +} + static void gemini_port_remove(struct gemini_ethernet_port *port) { + rtnl_lock(); + gemini_port_clear(port); + rtnl_unlock(); + if (port->netdev) { unregister_netdev(port->netdev); phy_disconnect(port->netdev->phydev);
@@ -2398,12 +2426,12 @@ static void gemini_port_remove(struct gemini_ethernet_port *port) static void gemini_ethernet_init(struct gemini_ethernet *geth) { + ASSERT_RTNL(); + /* Only do this once both ports are online */ if (geth->initialized) return; - if (geth->port0 && geth->port1) - geth->initialized = true; - else + if (!geth->port0 || !geth->port1) return; writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_0_REG);
@@ -2446,6 +2474,20 @@ static void gemini_ethernet_init(struct gemini_ethernet *geth) * set up and enable the queue IRQ. FIXME: fragile. */ geth->freeq_order = 1; + geth->initialized = true; +} + +static void gemini_port_publish(struct gemini_ethernet_port *port) +{ + struct gemini_ethernet *geth = port->geth; + + ASSERT_RTNL(); + + if (!port->id) + geth->port0 = port; + else + geth->port1 = port; + gemini_ethernet_init(geth); } static void gemini_port_save_mac_addr(struct gemini_ethernet_port *port)
@@ -2541,15 +2583,6 @@ static int gemini_ethernet_port_probe(struct platform_device *pdev) reset_control_reset(port->reset); usleep_range(100, 500); - /* Assign pointer in the main state container */ - if (!id) - geth->port0 = port; - else - geth->port1 = port; - - /* This will just be done once both ports are up and reset */ - gemini_ethernet_init(geth); - platform_set_drvdata(pdev, port); /* Set up and register the netdev */
@@ -2600,6 +2633,16 @@ static int gemini_ethernet_port_probe(struct platform_device *pdev) if (ret) goto unprepare; + if (!of_property_present(np, "phy-handle") && + !of_phy_is_fixed_link(np)) { + dev_info(dev, "no PHY, keeping port for shared IRQ\n"); + port->netdev = NULL; + rtnl_lock(); + gemini_port_publish(port); + rtnl_unlock(); + return 0; + } + ret = gmac_setup_phy(netdev); if (ret) { netdev_err(netdev,
@@ -2607,9 +2650,15 @@ static int gemini_ethernet_port_probe(struct platform_device *pdev) goto unprepare; } - ret = register_netdev(netdev); - if (ret) + rtnl_lock(); + ret = register_netdevice(netdev); + if (!ret) + gemini_port_publish(port); + rtnl_unlock(); + if (ret) { + phy_disconnect(netdev->phydev); goto unprepare; + } return 0;
@@ -2686,10 +2735,13 @@ static void gemini_ethernet_remove(struct platform_device *pdev) { struct gemini_ethernet *geth = platform_get_drvdata(pdev); + rtnl_lock(); + geth->initialized = false; + rtnl_unlock(); + devm_of_platform_depopulate(&pdev->dev); writel(0, geth->base + GLOBAL_INTERRUPT_ENABLE_4_REG); geth_cleanup_freeq(geth); - geth->initialized = false; } static const struct of_device_id gemini_ethernet_of_match[] = {
--
2.56.0