]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
batman-adv: extract netdev wifi detection information object
authorSven Eckelmann <sven@narfation.org>
Wed, 13 May 2026 19:37:46 +0000 (21:37 +0200)
committerSven Eckelmann <sven@narfation.org>
Mon, 1 Jun 2026 12:22:00 +0000 (14:22 +0200)
Previously, wifi_flags were stored directly in batadv_hard_iface, which is
created for every network interface on the system (including those never
attached to a mesh interface). This wastes memory and complicates the
long-term goal of lazily allocating batadv_hard_iface only for interfaces
that actually join a mesh.

The problem is that several batman-adv features need wifi detection for
net_devices (and their underlying devices) regardless of whether a
batadv_hard_iface exists for them:

* B.A.T.M.A.N. IV TQ hop penalty calculation
* B.A.T.M.A.N. V ELP probing / throughput estimation
* AP isolation

To decouple wifi detection from batadv_hard_iface lifetime, introduce a
global rhashtable (batadv_wifi_net_devices) mapping net_device pointers to
batadv_wifi_net_device_state objects. Only net_devices that are actually
detected as (indirect) wifi interfaces occupy an entry, keeping the common
(non-wifi) case allocation-free.

Signed-off-by: Sven Eckelmann <sven@narfation.org>
net/batman-adv/bat_v_elp.c
net/batman-adv/hard-interface.c
net/batman-adv/hard-interface.h
net/batman-adv/main.c
net/batman-adv/types.h

index fdc2abe96d777d7452d9e3ee9df5f737c7f03be4..e207093de79fe9ecbc9228cf444b217be7f26930 100644 (file)
@@ -85,6 +85,7 @@ static bool batadv_v_elp_get_throughput(struct batadv_hardif_neigh_node *neigh,
        struct ethtool_link_ksettings link_settings;
        struct net_device *real_netdev;
        struct station_info sinfo;
+       u32 wifi_flags;
        u32 throughput;
        int ret;
 
@@ -106,8 +107,9 @@ static bool batadv_v_elp_get_throughput(struct batadv_hardif_neigh_node *neigh,
        /* if this is a wireless device, then ask its throughput through
         * cfg80211 API
         */
-       if (batadv_is_wifi_hardif(hard_iface)) {
-               if (!batadv_is_cfg80211_hardif(hard_iface))
+       wifi_flags = batadv_hardif_get_wifi_flags(hard_iface);
+       if (batadv_is_wifi(wifi_flags)) {
+               if (!batadv_is_cfg80211(wifi_flags))
                        /* unsupported WiFi driver version */
                        goto default_throughput;
 
index 4b3804ef70b6fb3533671c5b94dbc74e67e1f72d..647fda1898e552044e68b817487f699a95862981 100644 (file)
@@ -26,6 +26,8 @@
 #include <linux/notifier.h>
 #include <linux/printk.h>
 #include <linux/rculist.h>
+#include <linux/rhashtable-types.h>
+#include <linux/rhashtable.h>
 #include <linux/rtnetlink.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 #include "send.h"
 #include "translation-table.h"
 
+static const struct rhashtable_params batadv_wifi_net_devices_params = {
+       .key_len                = sizeof(struct net_device *),
+       .key_offset             = offsetof(struct batadv_wifi_net_device_state, netdev),
+       .head_offset            = offsetof(struct batadv_wifi_net_device_state, l),
+       .automatic_shrinking    = true,
+};
+
+static struct rhashtable batadv_wifi_net_devices;
+
 /**
  * batadv_hardif_release() - release hard interface from lists and queue for
  *  free after rcu grace period
@@ -356,21 +367,28 @@ out:
 }
 
 /**
- * batadv_is_cfg80211_hardif() - check if the given hardif is a cfg80211 wifi
- *  interface
+ * batadv_hardif_get_wifi_flags() - retrieve wifi flags for hard_iface
  * @hard_iface: the device to check
  *
- * Return: true if the net device is a cfg80211 wireless device, false
- *  otherwise.
+ * Return: batadv_hard_iface_wifi_flags flags of the device
  */
-bool batadv_is_cfg80211_hardif(struct batadv_hard_iface *hard_iface)
+u32 batadv_hardif_get_wifi_flags(struct batadv_hard_iface *hard_iface)
 {
-       u32 allowed_flags = 0;
+       struct batadv_wifi_net_device_state *device_state;
+       u32 wifi_flags = 0;
+
+       if (!hard_iface)
+               return 0;
 
-       allowed_flags |= BATADV_HARDIF_WIFI_CFG80211_DIRECT;
-       allowed_flags |= BATADV_HARDIF_WIFI_CFG80211_INDIRECT;
+       rcu_read_lock();
+       device_state = rhashtable_lookup_fast(&batadv_wifi_net_devices,
+                                             &hard_iface->net_dev,
+                                             batadv_wifi_net_devices_params);
+       if (device_state)
+               wifi_flags = READ_ONCE(device_state->wifi_flags);
+       rcu_read_unlock();
 
-       return !!(hard_iface->wifi_flags & allowed_flags);
+       return wifi_flags;
 }
 
 /**
@@ -381,10 +399,9 @@ bool batadv_is_cfg80211_hardif(struct batadv_hard_iface *hard_iface)
  */
 bool batadv_is_wifi_hardif(struct batadv_hard_iface *hard_iface)
 {
-       if (!hard_iface)
-               return false;
+       u32 wifi_flags = batadv_hardif_get_wifi_flags(hard_iface);
 
-       return hard_iface->wifi_flags != 0;
+       return batadv_is_wifi(wifi_flags);
 }
 
 /**
@@ -890,7 +907,6 @@ batadv_hardif_add_interface(struct net_device *net_dev)
        kref_init(&hard_iface->refcount);
 
        hard_iface->num_bcasts = BATADV_NUM_BCASTS_DEFAULT;
-       hard_iface->wifi_flags = batadv_wifi_flags_evaluate(net_dev);
        if (batadv_is_wifi_hardif(hard_iface))
                hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS;
 
@@ -942,6 +958,131 @@ static int batadv_hard_if_event_meshif(unsigned long event,
        return NOTIFY_DONE;
 }
 
+/**
+ * batadv_wifi_net_device_insert() - save information about wifi net_device
+ * @net_dev: net_device to add to batadv_wifi_net_devices
+ * @wifi_flags: net_device which generated an event
+ *
+ * Return: 0 on result, negative value on error
+ */
+static int
+batadv_wifi_net_device_insert(struct net_device *net_dev, u32 wifi_flags)
+{
+       struct batadv_wifi_net_device_state *device_state;
+       int ret;
+
+       ASSERT_RTNL();
+
+       device_state = kzalloc_obj(*device_state, GFP_ATOMIC);
+       if (!device_state)
+               return -ENOMEM;
+
+       device_state->wifi_flags = wifi_flags;
+       netdev_hold(net_dev, &device_state->dev_tracker, GFP_ATOMIC);
+       device_state->netdev = net_dev;
+       WRITE_ONCE(device_state->wifi_flags, wifi_flags);
+
+       ret = rhashtable_insert_fast(&batadv_wifi_net_devices, &device_state->l,
+                                    batadv_wifi_net_devices_params);
+       if (ret < 0)
+               goto err_free;
+
+       return 0;
+
+err_free:
+       netdev_put(device_state->netdev, &device_state->dev_tracker);
+       kfree(device_state);
+       return ret;
+}
+
+/**
+ * batadv_wifi_net_device_remove() - remove information about wifi net_device
+ * @device_state: wifi net_device state to remove from batadv_wifi_net_device_state
+ */
+static void
+batadv_wifi_net_device_remove(struct batadv_wifi_net_device_state *device_state)
+{
+       ASSERT_RTNL();
+
+       rhashtable_remove_fast(&batadv_wifi_net_devices, &device_state->l,
+                              batadv_wifi_net_devices_params);
+       netdev_put(device_state->netdev, &device_state->dev_tracker);
+       kfree_rcu(device_state, rcu);
+}
+
+/**
+ * batadv_wifi_net_device_update() - update wifi state of net_device
+ * @net_dev: net_device to update in batadv_wifi_net_devices
+ *
+ * The device will only be stored in batadv_wifi_net_devices when
+ * it could be identified as wifi device. If the net_device is no
+ * longer a wifi device, it is automatically removed from
+ * batadv_wifi_net_devices.
+ */
+static void
+batadv_wifi_net_device_update(struct net_device *net_dev)
+{
+       struct batadv_wifi_net_device_state *device_state;
+       u32 wifi_flags;
+
+       ASSERT_RTNL();
+
+       wifi_flags = batadv_wifi_flags_evaluate(net_dev);
+       device_state = rhashtable_lookup_fast(&batadv_wifi_net_devices,
+                                             &net_dev,
+                                             batadv_wifi_net_devices_params);
+
+       if (device_state) {
+               if (batadv_is_wifi(wifi_flags))
+                       WRITE_ONCE(device_state->wifi_flags, wifi_flags);
+               else
+                       batadv_wifi_net_device_remove(device_state);
+       } else if (batadv_is_wifi(wifi_flags)) {
+               batadv_wifi_net_device_insert(net_dev, wifi_flags);
+       }
+}
+
+/**
+ * batadv_wifi_net_device_unregister() - remove wifi state of net_device
+ * @net_dev: net_device to remove from batadv_wifi_net_devices
+ */
+static void
+batadv_wifi_net_device_unregister(struct net_device *net_dev)
+{
+       struct batadv_wifi_net_device_state *device_state;
+
+       ASSERT_RTNL();
+
+       device_state = rhashtable_lookup_fast(&batadv_wifi_net_devices,
+                                             &net_dev,
+                                             batadv_wifi_net_devices_params);
+       if (!device_state)
+               return;
+
+       batadv_wifi_net_device_remove(device_state);
+}
+
+/**
+ * batadv_wifi_net_device_event() - handle network events for batadv_wifi_net_devices
+ * @event: enum netdev_cmd event to handle
+ * @net_dev: net_device to update in batadv_wifi_net_devices
+ */
+static void batadv_wifi_net_device_event(unsigned long event,
+                                        struct net_device *net_dev)
+{
+       switch (event) {
+       case NETDEV_REGISTER:
+       case NETDEV_POST_TYPE_CHANGE:
+       case NETDEV_CHANGEUPPER:
+               batadv_wifi_net_device_update(net_dev);
+               break;
+       case NETDEV_UNREGISTER:
+       case NETDEV_PRE_TYPE_CHANGE:
+               batadv_wifi_net_device_unregister(net_dev);
+               break;
+       }
+}
+
 static int batadv_hard_if_event(struct notifier_block *this,
                                unsigned long event, void *ptr)
 {
@@ -953,6 +1094,8 @@ static int batadv_hard_if_event(struct notifier_block *this,
        if (batadv_meshif_is_valid(net_dev))
                return batadv_hard_if_event_meshif(event, net_dev);
 
+       batadv_wifi_net_device_event(event, net_dev);
+
        hard_iface = batadv_hardif_get_by_netdev(net_dev);
        if (!hard_iface && (event == NETDEV_REGISTER ||
                            event == NETDEV_POST_TYPE_CHANGE))
@@ -996,8 +1139,9 @@ static int batadv_hard_if_event(struct notifier_block *this,
                if (hard_iface == primary_if)
                        batadv_primary_if_update_addr(bat_priv, NULL);
                break;
+       case NETDEV_REGISTER:
+       case NETDEV_POST_TYPE_CHANGE:
        case NETDEV_CHANGEUPPER:
-               hard_iface->wifi_flags = batadv_wifi_flags_evaluate(net_dev);
                if (batadv_is_wifi_hardif(hard_iface))
                        hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS;
                break;
@@ -1015,3 +1159,22 @@ out:
 struct notifier_block batadv_hard_if_notifier = {
        .notifier_call = batadv_hard_if_event,
 };
+
+/**
+ * batadv_wifi_net_devices_init() - Initialize wifi devices cache
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int __init batadv_wifi_net_devices_init(void)
+{
+       return rhashtable_init(&batadv_wifi_net_devices,
+                              &batadv_wifi_net_devices_params);
+}
+
+/**
+ * batadv_wifi_net_devices_deinit() - Deinitialize wifi devices cache
+ */
+void batadv_wifi_net_devices_deinit(void)
+{
+       rhashtable_destroy(&batadv_wifi_net_devices);
+}
index 9ba8fb2bdceb48f221d31333d7bcef765130062a..089e65c8a4817bc1b0dfcecbdd138029560d8665 100644 (file)
@@ -10,6 +10,7 @@
 #include "main.h"
 
 #include <linux/compiler.h>
+#include <linux/init.h>
 #include <linux/kref.h>
 #include <linux/netdevice.h>
 #include <linux/rcupdate.h>
@@ -69,7 +70,7 @@ extern struct notifier_block batadv_hard_if_notifier;
 
 struct net_device *__batadv_get_real_netdev(struct net_device *net_device);
 struct net_device *batadv_get_real_netdev(struct net_device *net_device);
-bool batadv_is_cfg80211_hardif(struct batadv_hard_iface *hard_iface);
+u32 batadv_hardif_get_wifi_flags(struct batadv_hard_iface *hard_iface);
 bool batadv_is_wifi_hardif(struct batadv_hard_iface *hard_iface);
 struct batadv_hard_iface*
 batadv_hardif_get_by_netdev(const struct net_device *net_dev);
@@ -81,6 +82,8 @@ void batadv_update_min_mtu(struct net_device *mesh_iface);
 void batadv_hardif_release(struct kref *ref);
 int batadv_hardif_no_broadcast(struct batadv_hard_iface *if_outgoing,
                               u8 *orig_addr, u8 *orig_neigh);
+int __init batadv_wifi_net_devices_init(void);
+void batadv_wifi_net_devices_deinit(void);
 
 /**
  * batadv_hardif_put() - decrement the hard interface refcounter and possibly
@@ -119,4 +122,33 @@ out:
        return hard_iface;
 }
 
+/**
+ * batadv_is_cfg80211() - check if the given hardif is a cfg80211
+ *  wifi interface
+ * @wifi_flags: extracted batadv_hard_iface_wifi_flagss of an net_device
+ *
+ * Return: true if the net device is a cfg80211 wireless device, false
+ *  otherwise.
+ */
+static inline bool batadv_is_cfg80211(u32 wifi_flags)
+{
+       u32 allowed_flags = 0;
+
+       allowed_flags |= BATADV_HARDIF_WIFI_CFG80211_DIRECT;
+       allowed_flags |= BATADV_HARDIF_WIFI_CFG80211_INDIRECT;
+
+       return !!(wifi_flags & allowed_flags);
+}
+
+/**
+ * batadv_is_wifi() - check if flags belong to wifi interface
+ * @wifi_flags: extracted batadv_hard_iface_wifi_flagss of an net_device
+ *
+ * Return: true if the net device is a 802.11 wireless device, false otherwise.
+ */
+static inline bool batadv_is_wifi(u32 wifi_flags)
+{
+       return wifi_flags != 0;
+}
+
 #endif /* _NET_BATMAN_ADV_HARD_INTERFACE_H_ */
index 82bba34893788ac81e26a4003742523b11643b1c..d529014857c904960f1f1f81a066bbedde6f521a 100644 (file)
@@ -105,8 +105,14 @@ static int __init batadv_init(void)
        batadv_tp_meter_init();
 
        batadv_event_workqueue = create_singlethread_workqueue("bat_events");
-       if (!batadv_event_workqueue)
+       if (!batadv_event_workqueue) {
+               ret = -ENOMEM;
                goto err_create_wq;
+       }
+
+       ret = batadv_wifi_net_devices_init();
+       if (ret < 0)
+               goto err_init_wifi;
 
        register_netdevice_notifier(&batadv_hard_if_notifier);
        rtnl_link_register(&batadv_link_ops);
@@ -117,10 +123,15 @@ static int __init batadv_init(void)
 
        return 0;
 
+err_init_wifi:
+       destroy_workqueue(batadv_event_workqueue);
+       batadv_event_workqueue = NULL;
+       rcu_barrier();
+
 err_create_wq:
        batadv_tt_cache_destroy();
 
-       return -ENOMEM;
+       return ret;
 }
 
 static void __exit batadv_exit(void)
@@ -134,6 +145,7 @@ static void __exit batadv_exit(void)
 
        rcu_barrier();
 
+       batadv_wifi_net_devices_deinit();
        batadv_tt_cache_destroy();
 }
 
index a01ee46d97f34f4c09242e94cf0d9bb93e944bcf..16fe8435774210291b94fcd031c579f34b95ce74 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/mutex.h>
 #include <linux/netdevice.h>
 #include <linux/netlink.h>
+#include <linux/rhashtable-types.h>
 #include <linux/sched.h> /* for linux/wait.h */
 #include <linux/skbuff.h>
 #include <linux/spinlock.h>
@@ -166,6 +167,29 @@ enum batadv_hard_iface_wifi_flags {
        BATADV_HARDIF_WIFI_CFG80211_INDIRECT = BIT(3),
 };
 
+/**
+ * struct batadv_wifi_net_device_state - cache of wifi information of net_devices
+ */
+struct batadv_wifi_net_device_state {
+       /** @l: anchor in rhashtable */
+       struct rhash_head l;
+
+       /** @netdev: pointer to the net_device */
+       struct net_device *netdev;
+
+       /** @dev_tracker: device tracker for @netdev */
+       netdevice_tracker dev_tracker;
+
+       /**
+        * @wifi_flags: flags whether this is (directly or indirectly) a wifi
+        *  interface
+        */
+       u32 wifi_flags;
+
+       /** @rcu: struct used for freeing in an RCU-safe manner */
+       struct rcu_head rcu;
+};
+
 /**
  * struct batadv_hard_iface - network device known to batman-adv
  */
@@ -181,12 +205,6 @@ struct batadv_hard_iface {
         */
        u8 num_bcasts;
 
-       /**
-        * @wifi_flags: flags whether this is (directly or indirectly) a wifi
-        *  interface
-        */
-       u32 wifi_flags;
-
        /** @net_dev: pointer to the net_device */
        struct net_device *net_dev;