]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: mac80211: extend support to fill link level sinfo structure
authorSarika Sharma <quic_sarishar@quicinc.com>
Wed, 28 May 2025 05:44:18 +0000 (11:14 +0530)
committerJohannes Berg <johannes.berg@intel.com>
Tue, 24 Jun 2025 13:19:26 +0000 (15:19 +0200)
Currently, sinfo structure is supported to fill information at
deflink( or one of the links) level for station. This has problems
when applied to fetch multi-link(ML) station information.

Hence, if valid_links are present, support filling link_station
structure for each link.

This will be helpful to check the link related statistics during MLO.

Additionally, TXQ stats for pertid are applicable at station level
not at link level. Therefore check link_id is less then 0, before
filling TXQ stats in pertid stats.

Signed-off-by: Sarika Sharma <quic_sarishar@quicinc.com>
Link: https://patch.msgid.link/20250528054420.3050133-9-quic_sarishar@quicinc.com
[fix some indentation]
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/cfg80211.h
net/mac80211/sta_info.c
net/wireless/util.c

index 7bf0c97d2ab139a65a4453da5d71ba001ff636ea..eec066f4738a4b86da0ae922cdf261acd2bc12b4 100644 (file)
@@ -8566,6 +8566,17 @@ void cfg80211_tx_mgmt_expired(struct wireless_dev *wdev, u64 cookie,
  */
 int cfg80211_sinfo_alloc_tid_stats(struct station_info *sinfo, gfp_t gfp);
 
+/**
+ * cfg80211_link_sinfo_alloc_tid_stats - allocate per-tid statistics.
+ *
+ * @link_sinfo: the link station information
+ * @gfp: allocation flags
+ *
+ * Return: 0 on success. Non-zero on error.
+ */
+int cfg80211_link_sinfo_alloc_tid_stats(struct link_station_info *link_sinfo,
+                                       gfp_t gfp);
+
 /**
  * cfg80211_sinfo_release_content - release contents of station info
  * @sinfo: the station information
index cf80b2fc889889b7d8405d1853ccd1aecd1dab1c..67af43d2e09bb9bea8c0f8bac01c23953408f4e4 100644 (file)
@@ -2634,7 +2634,7 @@ static void sta_set_tidstats(struct sta_info *sta,
                        link_sta_info->status_stats.msdu_failed[tid];
        }
 
-       if (tid < IEEE80211_NUM_TIDS) {
+       if (link_id < 0 && tid < IEEE80211_NUM_TIDS) {
                spin_lock_bh(&local->fq.lock);
                rcu_read_lock();
 
@@ -2719,13 +2719,249 @@ void sta_set_accumulated_removed_links_sinfo(struct sta_info *sta,
        }
 }
 
+static void sta_set_link_sinfo(struct sta_info *sta,
+                              struct link_station_info *link_sinfo,
+                              struct ieee80211_link_data *link,
+                              bool tidstats)
+{
+       struct ieee80211_sub_if_data *sdata = sta->sdata;
+       struct ieee80211_sta_rx_stats *last_rxstats;
+       int i, ac, cpu, link_id = link->link_id;
+       struct link_sta_info *link_sta_info;
+       u32 thr = 0;
+
+       last_rxstats = sta_get_last_rx_stats(sta, link_id);
+
+       link_sta_info = wiphy_dereference(sta->local->hw.wiphy,
+                                         sta->link[link_id]);
+
+       /* do before driver, so beacon filtering drivers have a
+        * chance to e.g. just add the number of filtered beacons
+        * (or just modify the value entirely, of course)
+        */
+       if (sdata->vif.type == NL80211_IFTYPE_STATION)
+               link_sinfo->rx_beacon = link->u.mgd.count_beacon_signal;
+
+       ether_addr_copy(link_sinfo->addr, link_sta_info->addr);
+
+       /* TODO: add drv_link_sta_statistics() ops to fill link_station
+        * statistics of station.
+        */
+
+       link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_INACTIVE_TIME) |
+                        BIT_ULL(NL80211_STA_INFO_BSS_PARAM) |
+                        BIT_ULL(NL80211_STA_INFO_RX_DROP_MISC);
+
+       if (sdata->vif.type == NL80211_IFTYPE_STATION) {
+               link_sinfo->beacon_loss_count =
+                       link->u.mgd.beacon_loss_count;
+               link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BEACON_LOSS);
+       }
+
+       link_sinfo->inactive_time =
+               jiffies_to_msecs(jiffies - ieee80211_sta_last_active(sta, link_id));
+
+       if (!(link_sinfo->filled & (BIT_ULL(NL80211_STA_INFO_TX_BYTES64) |
+                                   BIT_ULL(NL80211_STA_INFO_TX_BYTES)))) {
+               link_sinfo->tx_bytes = 0;
+               for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
+                       link_sinfo->tx_bytes +=
+                               link_sta_info->tx_stats.bytes[ac];
+               link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BYTES64);
+       }
+
+       if (!(link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_PACKETS))) {
+               link_sinfo->tx_packets = 0;
+               for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
+                       link_sinfo->tx_packets +=
+                               link_sta_info->tx_stats.packets[ac];
+               link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_PACKETS);
+       }
+
+       if (!(link_sinfo->filled & (BIT_ULL(NL80211_STA_INFO_RX_BYTES64) |
+                              BIT_ULL(NL80211_STA_INFO_RX_BYTES)))) {
+               link_sinfo->rx_bytes +=
+                       sta_get_stats_bytes(&link_sta_info->rx_stats);
+
+               if (link_sta_info->pcpu_rx_stats) {
+                       for_each_possible_cpu(cpu) {
+                               struct ieee80211_sta_rx_stats *cpurxs;
+
+                               cpurxs = per_cpu_ptr(link_sta_info->pcpu_rx_stats,
+                                                    cpu);
+                               link_sinfo->rx_bytes +=
+                                       sta_get_stats_bytes(cpurxs);
+                       }
+               }
+
+               link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BYTES64);
+       }
+
+       if (!(link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_RX_PACKETS))) {
+               link_sinfo->rx_packets = link_sta_info->rx_stats.packets;
+               if (link_sta_info->pcpu_rx_stats) {
+                       for_each_possible_cpu(cpu) {
+                               struct ieee80211_sta_rx_stats *cpurxs;
+
+                               cpurxs = per_cpu_ptr(link_sta_info->pcpu_rx_stats,
+                                                    cpu);
+                               link_sinfo->rx_packets += cpurxs->packets;
+                       }
+               }
+               link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_PACKETS);
+       }
+
+       if (!(link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_RETRIES))) {
+               link_sinfo->tx_retries =
+                       link_sta_info->status_stats.retry_count;
+               link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_RETRIES);
+       }
+
+       if (!(link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_FAILED))) {
+               link_sinfo->tx_failed =
+                       link_sta_info->status_stats.retry_failed;
+               link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_FAILED);
+       }
+
+       if (!(link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_RX_DURATION))) {
+               for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
+                       link_sinfo->rx_duration += sta->airtime[ac].rx_airtime;
+               link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_DURATION);
+       }
+
+       if (!(link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_DURATION))) {
+               for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
+                       link_sinfo->tx_duration += sta->airtime[ac].tx_airtime;
+               link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_DURATION);
+       }
+
+       if (!(link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_AIRTIME_WEIGHT))) {
+               link_sinfo->airtime_weight = sta->airtime_weight;
+               link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_AIRTIME_WEIGHT);
+       }
+
+       link_sinfo->rx_dropped_misc = link_sta_info->rx_stats.dropped;
+       if (link_sta_info->pcpu_rx_stats) {
+               for_each_possible_cpu(cpu) {
+                       struct ieee80211_sta_rx_stats *cpurxs;
+
+                       cpurxs = per_cpu_ptr(link_sta_info->pcpu_rx_stats,
+                                            cpu);
+                       link_sinfo->rx_dropped_misc += cpurxs->dropped;
+               }
+       }
+
+       if (sdata->vif.type == NL80211_IFTYPE_STATION &&
+           !(sdata->vif.driver_flags & IEEE80211_VIF_BEACON_FILTER)) {
+               link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_BEACON_RX) |
+                                BIT_ULL(NL80211_STA_INFO_BEACON_SIGNAL_AVG);
+               link_sinfo->rx_beacon_signal_avg =
+                       ieee80211_ave_rssi(&sdata->vif, -1);
+       }
+
+       if (ieee80211_hw_check(&sta->local->hw, SIGNAL_DBM) ||
+           ieee80211_hw_check(&sta->local->hw, SIGNAL_UNSPEC)) {
+               if (!(link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_SIGNAL))) {
+                       link_sinfo->signal = (s8)last_rxstats->last_signal;
+                       link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL);
+               }
+
+               if (!link_sta_info->pcpu_rx_stats &&
+                   !(link_sinfo->filled &
+                      BIT_ULL(NL80211_STA_INFO_SIGNAL_AVG))) {
+                       link_sinfo->signal_avg =
+                               -ewma_signal_read(&link_sta_info->rx_stats_avg.signal);
+                       link_sinfo->filled |=
+                               BIT_ULL(NL80211_STA_INFO_SIGNAL_AVG);
+               }
+       }
+
+       /* for the average - if pcpu_rx_stats isn't set - rxstats must point to
+        * the sta->rx_stats struct, so the check here is fine with and without
+        * pcpu statistics
+        */
+       if (last_rxstats->chains &&
+           !(link_sinfo->filled & (BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL) |
+                              BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG)))) {
+               link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL);
+               if (!link_sta_info->pcpu_rx_stats)
+                       link_sinfo->filled |=
+                               BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG);
+
+               link_sinfo->chains = last_rxstats->chains;
+
+               for (i = 0; i < ARRAY_SIZE(link_sinfo->chain_signal); i++) {
+                       link_sinfo->chain_signal[i] =
+                               last_rxstats->chain_signal_last[i];
+                       link_sinfo->chain_signal_avg[i] =
+                               -ewma_signal_read(
+                                       &link_sta_info->rx_stats_avg.chain_signal[i]);
+               }
+       }
+
+       if (!(link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_BITRATE)) &&
+           ieee80211_rate_valid(&link_sta_info->tx_stats.last_rate)) {
+               sta_set_rate_info_tx(sta, &link_sta_info->tx_stats.last_rate,
+                                    &link_sinfo->txrate);
+               link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE);
+       }
+
+       if (!(link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_RX_BITRATE))) {
+               if (sta_set_rate_info_rx(sta, &link_sinfo->rxrate,
+                                        link_id) == 0)
+                       link_sinfo->filled |=
+                               BIT_ULL(NL80211_STA_INFO_RX_BITRATE);
+       }
+
+       if (tidstats && !cfg80211_link_sinfo_alloc_tid_stats(link_sinfo,
+                                                            GFP_KERNEL)) {
+               for (i = 0; i < IEEE80211_NUM_TIDS + 1; i++)
+                       sta_set_tidstats(sta, &link_sinfo->pertid[i], i,
+                                        link_id);
+       }
+
+       link_sinfo->bss_param.flags = 0;
+       if (sdata->vif.bss_conf.use_cts_prot)
+               link_sinfo->bss_param.flags |= BSS_PARAM_FLAGS_CTS_PROT;
+       if (sdata->vif.bss_conf.use_short_preamble)
+               link_sinfo->bss_param.flags |= BSS_PARAM_FLAGS_SHORT_PREAMBLE;
+       if (sdata->vif.bss_conf.use_short_slot)
+               link_sinfo->bss_param.flags |= BSS_PARAM_FLAGS_SHORT_SLOT_TIME;
+       link_sinfo->bss_param.dtim_period = link->conf->dtim_period;
+       link_sinfo->bss_param.beacon_interval = link->conf->beacon_int;
+
+       thr = sta_get_expected_throughput(sta);
+
+       if (thr != 0) {
+               link_sinfo->filled |=
+                       BIT_ULL(NL80211_STA_INFO_EXPECTED_THROUGHPUT);
+               link_sinfo->expected_throughput = thr;
+       }
+
+       if (!(link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_ACK_SIGNAL)) &&
+           link_sta_info->status_stats.ack_signal_filled) {
+               link_sinfo->ack_signal =
+                       link_sta_info->status_stats.last_ack_signal;
+               link_sinfo->filled |= BIT_ULL(NL80211_STA_INFO_ACK_SIGNAL);
+       }
+
+       if (!(link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_ACK_SIGNAL_AVG)) &&
+           link_sta_info->status_stats.ack_signal_filled) {
+               link_sinfo->avg_ack_signal =
+                       -(s8)ewma_avg_signal_read(
+                               &link_sta_info->status_stats.avg_ack_signal);
+               link_sinfo->filled |=
+                       BIT_ULL(NL80211_STA_INFO_ACK_SIGNAL_AVG);
+       }
+}
+
 void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
                   bool tidstats)
 {
        struct ieee80211_sub_if_data *sdata = sta->sdata;
        struct ieee80211_local *local = sdata->local;
        u32 thr = 0;
-       int i, ac, cpu;
+       int i, ac, cpu, link_id;
        struct ieee80211_sta_rx_stats *last_rxstats;
 
        last_rxstats = sta_get_last_rx_stats(sta, -1);
@@ -2963,6 +3199,26 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
                sinfo->filled |=
                        BIT_ULL(NL80211_STA_INFO_ACK_SIGNAL_AVG);
        }
+
+       if (sta->sta.valid_links) {
+               struct ieee80211_link_data *link;
+               struct link_sta_info *link_sta;
+
+               ether_addr_copy(sinfo->mld_addr, sta->addr);
+               for_each_valid_link(sinfo, link_id) {
+                       link_sta = wiphy_dereference(sta->local->hw.wiphy,
+                                                    sta->link[link_id]);
+                       link = wiphy_dereference(sdata->local->hw.wiphy,
+                                                sdata->link[link_id]);
+
+                       if (!link_sta || !sinfo->links[link_id] || !link)
+                               continue;
+
+                       sinfo->valid_links = sta->sta.valid_links;
+                       sta_set_link_sinfo(sta, sinfo->links[link_id],
+                                          link, tidstats);
+               }
+       }
 }
 
 u32 sta_get_expected_throughput(struct sta_info *sta)
index e438f883f085b3aed0c0438d94957f08aa2c6688..5aff11c353034e34de04db8d7035a64c52037d81 100644 (file)
@@ -2650,6 +2650,18 @@ bool cfg80211_does_bw_fit_range(const struct ieee80211_freq_range *freq_range,
        return false;
 }
 
+int cfg80211_link_sinfo_alloc_tid_stats(struct link_station_info *link_sinfo,
+                                       gfp_t gfp)
+{
+       link_sinfo->pertid = kcalloc(IEEE80211_NUM_TIDS + 1,
+                                    sizeof(*link_sinfo->pertid), gfp);
+       if (!link_sinfo->pertid)
+               return -ENOMEM;
+
+       return 0;
+}
+EXPORT_SYMBOL(cfg80211_link_sinfo_alloc_tid_stats);
+
 int cfg80211_sinfo_alloc_tid_stats(struct station_info *sinfo, gfp_t gfp)
 {
        sinfo->pertid = kcalloc(IEEE80211_NUM_TIDS + 1,