[PATCH net-next v11 06/10] net: phy: phy_port: Store information about a port's upstream

From: Maxime Chevallier

Date: Thu May 21 2026 - 08:15:05 EST


MII phy_ports are not meant to be connected directly to a link partner.
They are meant to feed into some media converter devices that will
expose an MDI phy_port, so far we only support SFP modules for that.

In the case an MDI phy_port is backed by an MII port (e.g. a SFP
module's port, backed by the SFP cage port), let's keep track of the
port id of the MII port backing it.

Signed-off-by: Maxime Chevallier <maxime.chevallier@xxxxxxxxxxx>
---
drivers/net/phy/phy_device.c | 27 +++++++++++++++++++++++++++
drivers/net/phy/phylink.c | 5 +++++
include/linux/phy.h | 3 +++
include/linux/phy_port.h | 3 +++
4 files changed, 38 insertions(+)

diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 03bcaf5e5fe3..2a3fc69e0625 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1493,6 +1493,7 @@ static int phy_sfp_connect_phy(void *upstream, struct phy_device *phy)
int ret;

phydev->has_sfp_mod_phy = true;
+ phy_set_upstream_port(phy, phydev->sfp_cage_port);

/* If we aren't attached to a netdev, we can't add the SFP PHY to its
* topology.
@@ -1526,6 +1527,8 @@ static void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy)

if (dev)
phy_link_topo_del_phy(dev, phy);
+
+ phy_set_upstream_port(phy, NULL);
}

/**
@@ -1661,6 +1664,8 @@ static int phy_add_sfp_mod_port(struct phy_device *phydev)
*/
phydev->mod_port = port;

+ port->upstream_port = phydev->sfp_cage_port->id;
+
return 0;
}

@@ -3674,6 +3679,28 @@ struct phy_port *phy_get_sfp_port(struct phy_device *phydev)
}
EXPORT_SYMBOL_GPL(phy_get_sfp_port);

+/**
+ * phy_set_upstream_port() - Sets the phy_port controlling the MII this PHY is
+ * attached to.
+ * @phydev: pointer to the PHY device we set the upstream of.
+ * @port: The phy_port upstream of this PHY, can be NULL.
+ */
+void phy_set_upstream_port(struct phy_device *phydev, struct phy_port *port)
+{
+ struct phy_port *local_port;
+
+ ASSERT_RTNL();
+
+ phydev->upstream_port = port;
+
+ phy_for_each_port(phydev, local_port)
+ if (port)
+ local_port->upstream_port = port->id;
+ else
+ local_port->upstream_port = 0;
+}
+EXPORT_SYMBOL_GPL(phy_set_upstream_port);
+
/**
* fwnode_mdio_find_device - Given a fwnode, find the mdio_device
* @fwnode: pointer to the mdio_device's fwnode
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 59ea3a2e5da4..d069338e8e4d 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -3959,6 +3959,8 @@ static int phylink_add_sfp_mod_port(struct phylink *pl)
}
}

+ port->upstream_port = pl->sfp_cage_port->id;
+
pl->mod_port = port;

return 0;
@@ -4062,6 +4064,8 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces,
pl->config->supported_interfaces);

+ phy_set_upstream_port(phy, pl->sfp_cage_port);
+
/* Do the initial configuration */
return phylink_sfp_config_phy(pl, phy);
}
@@ -4070,6 +4074,7 @@ static void phylink_sfp_disconnect_phy(void *upstream,
struct phy_device *phydev)
{
phylink_disconnect_phy(upstream);
+ phy_set_upstream_port(phydev, NULL);
}

static const struct sfp_upstream_ops sfp_phylink_ops = {
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 59903257e978..b7d65e72e4c6 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -791,6 +791,7 @@ struct phy_device {
struct sfp_bus *sfp_bus;
struct phy_port *sfp_cage_port;
struct phy_port *mod_port;
+ struct phy_port *upstream_port;
struct phylink *phylink;
struct net_device *attached_dev;
struct mii_timestamper *mii_ts;
@@ -2466,6 +2467,8 @@ int __phy_hwtstamp_set(struct phy_device *phydev,

struct phy_port *phy_get_sfp_port(struct phy_device *phydev);

+void phy_set_upstream_port(struct phy_device *phydev, struct phy_port *port);
+
/**
* phy_module_driver() - Helper macro for registering PHY drivers
* @__phy_drivers: array of PHY drivers to register
diff --git a/include/linux/phy_port.h b/include/linux/phy_port.h
index 4e2a3fdd2f2e..e3a41cedebdc 100644
--- a/include/linux/phy_port.h
+++ b/include/linux/phy_port.h
@@ -40,6 +40,8 @@ struct phy_port_ops {
* @head: Used by the port's parent to list ports
* @parent_type: The type of device this port is directly connected to
* @phy: If the parent is PHY_PORT_PHYDEV, the PHY controlling that port
+ * @upstream_port: For non-MII ports, indicates the MII port that feeds this
+ * port, e.g. the SFP cage port for a SFP module port.
* @ops: Callback ops implemented by the port controller
* @pairs: The number of pairs this port has, 0 if not applicable
* @mediums: Bitmask of the physical mediums this port provides access to
@@ -59,6 +61,7 @@ struct phy_port {
union {
struct phy_device *phy;
};
+ u32 upstream_port;

const struct phy_port_ops *ops;

--
2.54.0