Now that the 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 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 | 88 +++++++++++++++++++++++++++++++++++- include/linux/phy.h | 6 +++ 4 files changed, 121 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 *supported, 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 long *from, + const unsigned long *interfaces); #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 *linkmodes) 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 achieve + * 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) = {}; + unsigned int ifaces_caps = 0; + phy_interface_t interface; + + for_each_set_bit(interface, interfaces, PHY_INTERFACE_MODE_MAX) + ifaces_caps |= 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 b0971f57686c..0daa87413c66 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1483,6 +1483,8 @@ static int phy_sfp_connect_phy(void *upstream, struct phy_device *phy) struct phy_device *phydev = upstream; struct net_device *dev = phydev->attached_dev; + phydev->has_sfp_mod_phy = true; + if (dev) return phy_link_topo_add_phy(dev, phy, PHY_UPSTREAM_PHY, phydev); @@ -1504,6 +1506,8 @@ static void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy) struct phy_device *phydev = upstream; struct net_device *dev = phydev->attached_dev; + phydev->has_sfp_mod_phy = false; + if (dev) phy_link_topo_del_phy(dev, phy); } @@ -1609,6 +1613,74 @@ static void phy_sfp_link_down(void *upstream) port->ops->link_down(port); } +static int phy_add_sfp_mod_port(struct phy_device *phydev) +{ + const struct sfp_module_caps *caps; + struct phy_port *port; + int ret = 0; + + /* Create mod port */ + port = phy_port_alloc(); + if (!port) + return -ENOMEM; + + port->active = true; + + caps = 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 = 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 = port; + + return 0; +} + +static void phy_del_sfp_mod_port(struct phy_device *phydev) +{ + if (!phydev->mod_port) + return; + + if (phydev->attached_dev) + phy_link_topo_del_port(phydev->attached_dev, phydev->mod_port); + + phy_port_destroy(phydev->mod_port); + phydev->mod_port = NULL; +} + +static int phy_sfp_module_start(void *upstream) +{ + struct phy_device *phydev = upstream; + + /* If there's a downstream SFP module, and it doesn't contain a PHY + * device, let's create a phy_port to represent that module. + */ + if (!phydev->has_sfp_mod_phy) + return phy_add_sfp_mod_port(phydev); + + return 0; +} + +static void phy_sfp_module_stop(void *upstream) +{ + struct phy_device *phydev = upstream; + + if (!phydev->has_sfp_mod_phy) + phy_del_sfp_mod_port(phydev); +} + static const struct sfp_upstream_ops sfp_phydev_ops = { .attach = phy_sfp_attach, .detach = phy_sfp_detach, @@ -1618,6 +1690,8 @@ static const struct sfp_upstream_ops sfp_phydev_ops = { .link_down = phy_sfp_link_down, .connect_phy = phy_sfp_connect_phy, .disconnect_phy = phy_sfp_disconnect_phy, + .module_start = phy_sfp_module_start, + .module_stop = phy_sfp_module_stop, }; static int phy_add_port(struct phy_device *phydev, struct phy_port *port) @@ -1700,7 +1774,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 = NULL; struct sfp_bus *bus; int ret; @@ -1727,8 +1801,11 @@ static int phy_sfp_probe(struct phy_device *phydev) if (ret && port) { phy_del_port(phydev, port); phy_port_destroy(port); + port = NULL; } + phydev->sfp_bus_port = port; + return ret; } @@ -1818,6 +1895,12 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, err = phy_link_topo_add_phy(dev, phydev, PHY_UPSTREAM_MAC, dev); if (err) goto error; + + if (phydev->mod_port) { + err = phy_link_topo_add_port(dev, phydev->mod_port); + if (err) + goto error; + } } /* Some Ethernet drivers try to connect to a PHY device before @@ -1991,6 +2074,8 @@ void phy_detach(struct phy_device *phydev) phydev->attached_dev->phydev = NULL; phydev->attached_dev = NULL; phy_link_topo_del_phy(dev, phydev); + if (phydev->mod_port) + phy_link_topo_del_port(dev, phydev->mod_port); } phydev->phy_link_change = NULL; @@ -3821,6 +3906,7 @@ static int phy_remove(struct device *dev) sfp_bus_del_upstream(phydev->sfp_bus); phydev->sfp_bus = NULL; + phydev->sfp_bus_port = NULL; if (phydev->drv && phydev->drv->remove) phydev->drv->remove(phydev); diff --git a/include/linux/phy.h b/include/linux/phy.h index 6f9979a26892..9114fdff4c4f 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -582,6 +582,7 @@ struct phy_oatc14_sqi_capability { * @wol_enabled: Set to true if the PHY or the attached MAC have Wake-on-LAN * enabled. * @is_genphy_driven: PHY is driven by one of the generic PHY drivers + * @has_sfp_mod_phy: Set true if downstream SFP bus's module contains a PHY * @state: State of the PHY for management purposes * @dev_flags: Device-specific flags used by the PHY driver. * @@ -594,6 +595,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: in the * link state. @@ -704,6 +707,7 @@ struct phy_device { unsigned irq_rerun:1; unsigned default_timestamp:1; + unsigned has_sfp_mod_phy:1; int rate_matching; @@ -782,6 +786,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; -- 2.49.0