From: Sven Eckelmann Date: Wed, 13 May 2026 19:37:46 +0000 (+0200) Subject: batman-adv: extract netdev wifi detection information object X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c0559465842cd0160bb018d16c902c68f4b21648;p=thirdparty%2Flinux.git batman-adv: extract netdev wifi detection information object 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 --- diff --git a/net/batman-adv/bat_v_elp.c b/net/batman-adv/bat_v_elp.c index fdc2abe96d777..e207093de79fe 100644 --- a/net/batman-adv/bat_v_elp.c +++ b/net/batman-adv/bat_v_elp.c @@ -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; diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index 4b3804ef70b6f..647fda1898e55 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include #include @@ -43,6 +45,15 @@ #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); +} diff --git a/net/batman-adv/hard-interface.h b/net/batman-adv/hard-interface.h index 9ba8fb2bdceb4..089e65c8a4817 100644 --- a/net/batman-adv/hard-interface.h +++ b/net/batman-adv/hard-interface.h @@ -10,6 +10,7 @@ #include "main.h" #include +#include #include #include #include @@ -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_ */ diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index 82bba34893788..d529014857c90 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -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(); } diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index a01ee46d97f34..16fe843577421 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -21,6 +21,7 @@ #include #include #include +#include #include /* for linux/wait.h */ #include #include @@ -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;