From: Amitesh Singh <[email protected]>

batadv_v_elp_get_throughput() calls __ethtool_get_link_ksettings() to
determine the link speed of an Ethernet interface. Virtual interfaces
such as a VLAN on a bridge return success but with SPEED_UNKNOWN, so the
function falls through to the hardcoded default even when the underlying
physical device has a known speed.

Move rtnl_unlock() into each exit branch so the lock is still held
after the initial query. When the speed is unknown, walk the lower
device stack with netdev_walk_all_lower_dev() to find the first real
device that reports a valid speed, which covers DSA ports and physical
NICs sitting below a VLAN or bridge interface.

Signed-off-by: Amitesh Singh <[email protected]>
---
 net/batman-adv/bat_v_elp.c | 48 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 47 insertions(+), 1 deletion(-)

diff --git a/net/batman-adv/bat_v_elp.c b/net/batman-adv/bat_v_elp.c
index 6ad6042a..3d4d82c5 100644
--- a/net/batman-adv/bat_v_elp.c
+++ b/net/batman-adv/bat_v_elp.c
@@ -70,6 +70,32 @@ static void batadv_v_elp_start_timer(struct 
batadv_hard_iface *hard_iface)
                           msecs_to_jiffies(msecs));
 }
 
+/**
+ * batadv_v_elp_lower_dev_throughput() - netdev_walk_all_lower_dev callback
+ *  to query ethtool link speed on a lower device in the stack
+ * @dev: current lower device being visited
+ * @priv: nested priv; priv->data points to a u32 to receive the throughput
+ *  value in multiples of 100kbps
+ *
+ * Return: 1 to stop the walk once a valid speed is found, 0 to continue.
+ */
+static int batadv_v_elp_lower_dev_throughput(struct net_device *dev,
+                                            struct netdev_nested_priv *priv)
+{
+       struct ethtool_link_ksettings settings;
+       u32 speed;
+
+       if (__ethtool_get_link_ksettings(dev, &settings) != 0)
+               return 0;
+
+       speed = settings.base.speed;
+       if (!speed || speed == SPEED_UNKNOWN)
+               return 0;
+
+       *(u32 *)priv->data = speed * 10;
+       return 1;
+}
+
 /**
  * batadv_v_elp_get_throughput() - get the throughput towards a neighbour
  * @neigh: the neighbour for which the throughput has to be obtained
@@ -167,7 +193,6 @@ static bool batadv_v_elp_get_throughput(struct 
batadv_hardif_neigh_node *neigh,
         * ethtool (e.g. an Ethernet adapter)
         */
        ret = __ethtool_get_link_ksettings(hard_iface->net_dev, &link_settings);
-       rtnl_unlock();
        if (ret == 0) {
                /* link characteristics might change over time */
                if (link_settings.base.duplex == DUPLEX_FULL)
@@ -177,9 +202,30 @@ static bool batadv_v_elp_get_throughput(struct 
batadv_hardif_neigh_node *neigh,
 
                throughput = link_settings.base.speed;
                if (throughput && throughput != SPEED_UNKNOWN) {
+                       rtnl_unlock();
                        *pthroughput = throughput * 10;
                        return true;
                }
+
+               /* Virtual interfaces (e.g. VLAN on bridge) don't report link
+                * speed directly. Walk the lower device stack to find a real
+                * device that does, such as a DSA port or physical NIC.
+                */
+               throughput = 0;
+               {
+                       struct netdev_nested_priv priv = { .data = &throughput 
};
+
+                       netdev_walk_all_lower_dev(hard_iface->net_dev,
+                                                 
batadv_v_elp_lower_dev_throughput,
+                                                 &priv);
+               }
+               rtnl_unlock();
+               if (throughput) {
+                       *pthroughput = throughput;
+                       return true;
+               }
+       } else {
+               rtnl_unlock();
        }
 
 default_throughput:
-- 
2.43.0

Reply via email to