diff options
Diffstat (limited to 'drivers/net/phy/phy_link_topology.c')
| -rw-r--r-- | drivers/net/phy/phy_link_topology.c | 101 |
1 files changed, 93 insertions, 8 deletions
diff --git a/drivers/net/phy/phy_link_topology.c b/drivers/net/phy/phy_link_topology.c index 4134de7ae313..214ccce23cf6 100644 --- a/drivers/net/phy/phy_link_topology.c +++ b/drivers/net/phy/phy_link_topology.c @@ -7,6 +7,7 @@ */ #include <linux/phy_link_topology.h> +#include <linux/phy_port.h> #include <linux/phy.h> #include <linux/rtnetlink.h> #include <linux/xarray.h> @@ -23,17 +24,89 @@ static int netdev_alloc_phy_link_topology(struct net_device *dev) xa_init_flags(&topo->phys, XA_FLAGS_ALLOC1); topo->next_phy_index = 1; + xa_init_flags(&topo->ports, XA_FLAGS_ALLOC1); + topo->next_port_index = 1; + dev->link_topo = topo; return 0; } +static struct phy_link_topology *phy_link_topo_get_or_alloc(struct net_device *dev) +{ + int ret; + + if (dev->link_topo) + return dev->link_topo; + + /* The topology is allocated the first time we add an object to it. + * It is freed alongside the netdev. It can be called on multiple + * contexts: + * - It can be called from .probe() : No rtnl, no netdev_lock + * - .ndo_open() : rtnl and possibly netdev_lock + * - SFP state machine : rtnl held or not + * + * However, we can't really have races : + * - If we have a PHY, phy_link_topo_add_phy() will always run first + * and trigger the alloc. Only then the ports can be added through + * phylib or sfp. + * - If we don't, the SFP port for the cage is registered first, and + * only then other ports/PHYs can be registered. + */ + ret = netdev_alloc_phy_link_topology(dev); + if (ret) + return ERR_PTR(ret); + + return dev->link_topo; +} + +int phy_link_topo_add_port(struct net_device *dev, struct phy_port *port) +{ + struct phy_link_topology *topo; + int ret; + + /* Ports can now be queried without rtnl for ops-locked devices, which + * we don't support now as port retrieval is done under rtnl. + * We don't have phy_port enabled devices yet, let's make sure + * we are loudly warned about that when it happens. + */ + if (WARN_ON_ONCE(dev->netdev_ops && netdev_need_ops_lock(dev))) + return -EOPNOTSUPP; + + topo = phy_link_topo_get_or_alloc(dev); + if (IS_ERR(topo)) + return PTR_ERR(topo); + + /* Attempt to re-use a previously allocated port_id */ + if (port->id) + ret = xa_insert(&topo->ports, port->id, port, GFP_KERNEL); + else + ret = xa_alloc_cyclic(&topo->ports, &port->id, port, + xa_limit_32b, &topo->next_port_index, + GFP_KERNEL); + + return ret < 0 ? ret : 0; +} +EXPORT_SYMBOL_GPL(phy_link_topo_add_port); + +void phy_link_topo_del_port(struct net_device *dev, struct phy_port *port) +{ + struct phy_link_topology *topo = dev->link_topo; + + if (!topo) + return; + + xa_erase(&topo->ports, port->id); +} +EXPORT_SYMBOL_GPL(phy_link_topo_del_port); + int phy_link_topo_add_phy(struct net_device *dev, struct phy_device *phy, enum phy_upstream upt, void *upstream) { - struct phy_link_topology *topo = dev->link_topo; + struct phy_link_topology *topo; struct phy_device_node *pdn; + struct phy_port *port; int ret; /* ethtool ops may run without rtnl_lock, and rtnl_lock is what @@ -45,13 +118,9 @@ int phy_link_topo_add_phy(struct net_device *dev, if (WARN_ON_ONCE(netdev_need_ops_lock(dev))) return -EOPNOTSUPP; - if (!topo) { - ret = netdev_alloc_phy_link_topology(dev); - if (ret) - return ret; - - topo = dev->link_topo; - } + topo = phy_link_topo_get_or_alloc(dev); + if (IS_ERR(topo)) + return PTR_ERR(topo); pdn = kzalloc_obj(*pdn); if (!pdn) @@ -86,8 +155,20 @@ int phy_link_topo_add_phy(struct net_device *dev, if (ret < 0) goto err; + /* Add all the PHY's ports to the topology */ + list_for_each_entry(port, &phy->ports, head) { + ret = phy_link_topo_add_port(dev, port); + if (ret) + goto del_ports; + } + return 0; +del_ports: + list_for_each_entry_continue_reverse(port, &phy->ports, head) + phy_link_topo_del_port(dev, port); + + xa_erase(&topo->phys, phy->phyindex); err: kfree(pdn); return ret; @@ -99,10 +180,14 @@ void phy_link_topo_del_phy(struct net_device *dev, { struct phy_link_topology *topo = dev->link_topo; struct phy_device_node *pdn; + struct phy_port *port; if (!topo) return; + list_for_each_entry(port, &phy->ports, head) + phy_link_topo_del_port(dev, port); + pdn = xa_erase(&topo->phys, phy->phyindex); /* We delete the PHY from the topology, however we don't re-set the |
