From nobody Mon Feb 9 13:37:23 2026 Received: from smtpout-04.galae.net (smtpout-04.galae.net [185.171.202.116]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8A9B5376BC6 for ; Wed, 28 Jan 2026 20:45:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.171.202.116 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769633164; cv=none; b=E79gqfv+K9SBE635ngR58mZjDkDXGOG8ANAU4Skbls977wkvxXO5YR2Y+NALkgr5slaKqqsWAT+x2PHrPqE9yZUsLogYB68hUBoadxnDtOAq86G8cJV0XF36OLLZE5cLYKy7Pc3T/u/5N/OMnEOOcZMSbSo6V6Q9xyddMM0I680= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769633164; c=relaxed/simple; bh=5LnkBfoB7QllJKIseqDIe8FHMXiM9rQixryHtaI8fjg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UmpWuXY4DDLVJP3uEMGhR8jvkgHegrst59KnDGowauvoiBenvltcFwHd7mSM29Glrs/bBmKqQjT+mcUOzH/DQ+wW1y+xD3uV5nSyxQ9MMKyvFBajRmM9K+aL21nXalbPOzAjgpGpDsVcD3wjiEUkciyNbfyZxls3A1FyBg8AbxM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=F/267Jah; arc=none smtp.client-ip=185.171.202.116 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="F/267Jah" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-04.galae.net (Postfix) with ESMTPS id D6599C211F1; Wed, 28 Jan 2026 20:45:58 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 37EC16071F; Wed, 28 Jan 2026 20:45:56 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id 11C9A119A8835; Wed, 28 Jan 2026 21:45:53 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1769633155; h=from:subject:date:message-id:to:cc:mime-version: content-transfer-encoding:in-reply-to:references; bh=vSUr6ReAFYTUjAYmZ597frc70URndFFYWhKPXvo42Xs=; b=F/267JahJA6LbhFSM5jVVFPGtd7fZhMsSu1+9wQWhcpA3hfizGoAkTuI+eGWGHS08+cF+0 eiVNuwYw0MkOnZGN8EjYp+6Qz/Di7QggifGdGXaEKsHYIF/bUOJ/5ac8qwq2Z/h1dCYVZR sfwUIuXrVSd0YkGRHiM9g70U+B+pF9vK/6bzB/VGs89DaqA3bRlUtRXGyV9Mc1VBmp2hO8 N8SNxMRH/USczA30d9IFGRCZm8qWv1jksQYEFoBVHYFZlmzEd8DXzJO/+JfvQ+Q3j2BaqT vGWFZVpJwXGYXrsGZP+CVQXCo0boYaJmqOf8tpi4L2kkE/t0bwWiOuKnoX85dg== From: Maxime Chevallier To: davem@davemloft.net, Andrew Lunn , Jakub Kicinski , Eric Dumazet , Paolo Abeni , Russell King , Heiner Kallweit Cc: Maxime Chevallier , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, thomas.petazzoni@bootlin.com, Christophe Leroy , Herve Codina , Florian Fainelli , Vladimir Oltean , =?UTF-8?q?K=C3=B6ry=20Maincent?= , =?UTF-8?q?Marek=20Beh=C3=BAn?= , Oleksij Rempel , =?UTF-8?q?Nicol=C3=B2=20Veronese?= , Simon Horman , mwojtas@chromium.org, Romain Gantois , Daniel Golle , Dimitri Fedrau Subject: [PATCH net-next v2 07/12] net: phy: Represent PHY-less SFP modules with phy_port Date: Wed, 28 Jan 2026 21:45:20 +0100 Message-ID: <20260128204526.170927-8-maxime.chevallier@bootlin.com> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20260128204526.170927-1-maxime.chevallier@bootlin.com> References: <20260128204526.170927-1-maxime.chevallier@bootlin.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Last-TLS-Session-Version: TLSv1.3 Content-Type: text/plain; charset="utf-8" Now that th SFP bus infrastructure notifies when PHY-less modules are connected, we can create a phy_port to represent it. Instead of letting the SFP subsystem handle that, the Bus' upstream is in charge of maintaining that phy_port and register it to the topology, as the upstream (in this case a phy device) is directly interacting with the underlying net_device. Add a phy_caps helper alongside to get the achievable modes on this module based on what the phy_port representing the bus supports. Signed-off-by: Maxime Chevallier --- drivers/net/phy/phy-caps.h | 2 ++ drivers/net/phy/phy_caps.c | 26 +++++++++++++++ drivers/net/phy/phy_device.c | 62 +++++++++++++++++++++++++++++++++++- include/linux/phy.h | 4 +++ 4 files changed, 93 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/phy-caps.h b/drivers/net/phy/phy-caps.h index 421088e6f6e8..ec3d39a0ae06 100644 --- a/drivers/net/phy/phy-caps.h +++ b/drivers/net/phy/phy-caps.h @@ -66,5 +66,7 @@ void phy_caps_medium_get_supported(unsigned long *support= ed, enum ethtool_link_medium medium, int lanes); u32 phy_caps_mediums_from_linkmodes(unsigned long *linkmodes); +void phy_caps_linkmode_filter_ifaces(unsigned long *to, const unsigned lon= g *from, + const unsigned long *interfaces); =20 #endif /* __PHY_CAPS_H */ diff --git a/drivers/net/phy/phy_caps.c b/drivers/net/phy/phy_caps.c index 942d43191561..558e4df4d63c 100644 --- a/drivers/net/phy/phy_caps.c +++ b/drivers/net/phy/phy_caps.c @@ -445,3 +445,29 @@ u32 phy_caps_mediums_from_linkmodes(unsigned long *lin= kmodes) return mediums; } EXPORT_SYMBOL_GPL(phy_caps_mediums_from_linkmodes); + +/** + * phy_caps_linkmode_filter_ifaces() - Filter linkmodes with an interface = list + * @to: Stores the filtered linkmodes + * @from: Linkmodes to filter + * @interfaces: Bitfield of phy_interface_t that we use for filtering + * + * Filter the provided linkmodes, only to keep the ones we can possibly ac= hieve + * when using any of the provided MII interfaces. + */ +void phy_caps_linkmode_filter_ifaces(unsigned long *to, + const unsigned long *from, + const unsigned long *interfaces) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(ifaces_supported) =3D {}; + unsigned int ifaces_caps =3D 0; + phy_interface_t interface; + + for_each_set_bit(interface, interfaces, PHY_INTERFACE_MODE_MAX) + ifaces_caps |=3D phy_caps_from_interface(interface); + + phy_caps_linkmodes(ifaces_caps, ifaces_supported); + + linkmode_and(to, from, ifaces_supported); +} +EXPORT_SYMBOL_GPL(phy_caps_linkmode_filter_ifaces); diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 49696b1a2f13..b3125cf30bf3 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1609,6 +1609,53 @@ static void phy_sfp_link_down(void *upstream) port->ops->link_down(port); } =20 +static int phy_sfp_connect_nophy(void *upstream) +{ + struct phy_device *phydev =3D upstream; + const struct sfp_module_caps *caps; + struct phy_port *port; + int ret =3D 0; + + /* Create mod port */ + port =3D phy_port_alloc(); + if (!port) + return -ENOMEM; + + port->active =3D true; + + caps =3D sfp_get_module_caps(phydev->sfp_bus); + + phy_caps_linkmode_filter_ifaces(port->supported, caps->link_modes, + phydev->sfp_bus_port->interfaces); + + if (phydev->attached_dev) { + ret =3D phy_link_topo_add_port(phydev->attached_dev, port); + if (ret) { + phy_port_destroy(port); + return ret; + } + } + + /* we don't use phy_add_port() here as the module port isn't a direct + * interface from the PHY, but rather an extension to the sfp-bus, that + * is already represented by its own phy_port + */ + phydev->mod_port =3D port; + + return 0; +} + +static void phy_sfp_disconnect_nophy(void *upstream) +{ + struct phy_device *phydev =3D upstream; + + if (phydev->attached_dev) + phy_link_topo_del_port(phydev->attached_dev, phydev->mod_port); + + phy_port_destroy(phydev->mod_port); + phydev->mod_port =3D NULL; +} + static const struct sfp_upstream_ops sfp_phydev_ops =3D { .attach =3D phy_sfp_attach, .detach =3D phy_sfp_detach, @@ -1618,6 +1665,8 @@ static const struct sfp_upstream_ops sfp_phydev_ops = =3D { .link_down =3D phy_sfp_link_down, .connect_phy =3D phy_sfp_connect_phy, .disconnect_phy =3D phy_sfp_disconnect_phy, + .connect_nophy =3D phy_sfp_connect_nophy, + .disconnect_nophy =3D phy_sfp_disconnect_nophy, }; =20 static int phy_add_port(struct phy_device *phydev, struct phy_port *port) @@ -1700,7 +1749,7 @@ static struct phy_port *phy_setup_sfp_port(struct phy= _device *phydev) */ static int phy_sfp_probe(struct phy_device *phydev) { - struct phy_port *port; + struct phy_port *port =3D NULL; struct sfp_bus *bus; int ret; =20 @@ -1729,6 +1778,8 @@ static int phy_sfp_probe(struct phy_device *phydev) phy_port_destroy(port); } =20 + phydev->sfp_bus_port =3D port; + return ret; } =20 @@ -1818,6 +1869,12 @@ int phy_attach_direct(struct net_device *dev, struct= phy_device *phydev, err =3D phy_link_topo_add_phy(dev, phydev, PHY_UPSTREAM_MAC, dev); if (err) goto error; + + if (phydev->mod_port) { + err =3D phy_link_topo_add_port(dev, phydev->mod_port); + if (err) + goto error; + } } =20 /* Some Ethernet drivers try to connect to a PHY device before @@ -1991,6 +2048,8 @@ void phy_detach(struct phy_device *phydev) phydev->attached_dev->phydev =3D NULL; phydev->attached_dev =3D NULL; phy_link_topo_del_phy(dev, phydev); + if (phydev->mod_port) + phy_link_topo_del_port(dev, phydev->mod_port); } =20 phydev->phy_link_change =3D NULL; @@ -3818,6 +3877,7 @@ static int phy_remove(struct device *dev) =20 sfp_bus_del_upstream(phydev->sfp_bus); phydev->sfp_bus =3D NULL; + phydev->sfp_bus_port =3D NULL; =20 if (phydev->drv && phydev->drv->remove) phydev->drv->remove(phydev); diff --git a/include/linux/phy.h b/include/linux/phy.h index 6f9979a26892..dc788ae4da64 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -594,6 +594,8 @@ struct phy_oatc14_sqi_capability { * @phylink: Pointer to phylink instance for this PHY * @sfp_bus_attached: Flag indicating whether the SFP bus has been attached * @sfp_bus: SFP bus attached to this PHY's fiber port + * @sfp_bus_port: The phy_port connected to the downstream SFP bus + * @mod_port: phy_port representing the SFP module, if it is phy-less * @attached_dev: The attached enet driver's device instance ptr * @adjust_link: Callback for the enet controller to respond to changes: i= n the * link state. @@ -782,6 +784,8 @@ struct phy_device { /* This may be modified under the rtnl lock */ bool sfp_bus_attached; struct sfp_bus *sfp_bus; + struct phy_port *sfp_bus_port; + struct phy_port *mod_port; struct phylink *phylink; struct net_device *attached_dev; struct mii_timestamper *mii_ts; --=20 2.49.0