2015-01-16 09:48:36

by Johannes Berg

[permalink] [raw]
Subject: [RFC 1/2] nl80211: support per-rate/per-station statistics

From: Johannes Berg <[email protected]>

Per-rate/per-station statistics can be desirable to have but they're
quite expensive (keeping the four counters for each rate would take
close to 4k of memory per station only for VHT MCSes for a moderately
capable VHT chip (with 2 spatial streams and 80MHz support) so it's
not a good idea to keep all of this in the kernel. Using a hash table
instead can be done, but then addressing becomes more expensive.

Instead, this API provides a way for interested clients in userspace
to subscribe to such statistics. When supported by a driver, it can
then start collecting the data only when subscribers exist. To avoid
the kernel's data collection becoming too big, it can send out the
data at any point in time, for example to limit the counters to u32
internally and send it out when they're close to reaching the limit,
or to keep a hash table and sending it out when too many collisions
occur. Userspace can then keep track of the full state.

Signed-off-by: Johannes Berg <[email protected]>
---
include/net/cfg80211.h | 43 +++++++++
include/uapi/linux/nl80211.h | 76 ++++++++++++++++
net/wireless/core.c | 17 ++++
net/wireless/nl80211.c | 208 +++++++++++++++++++++++++++++++++++++------
net/wireless/nl80211.h | 15 ++++
net/wireless/rdev-ops.h | 10 +++
net/wireless/trace.h | 14 +++
7 files changed, 355 insertions(+), 28 deletions(-)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 7b44ba0a7632..0b9ac3af1853 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -2143,6 +2143,21 @@ struct cfg80211_qos_map {
};

/**
+ * enum cfg80211_rate_stats_ops - rate statistics operations
+ *
+ * @CFG80211_RATE_STATS_START: start data collection
+ * @CFG80211_RATE_STATS_DUMP: atomically dump and clear the data
+ * (using cfg80211_report_rate_stats() to report it); this
+ * should be done before the function call returns
+ * @CFG80211_RATE_STATS_STOP: stop data collection
+ */
+enum cfg80211_rate_stats_ops {
+ CFG80211_RATE_STATS_START,
+ CFG80211_RATE_STATS_DUMP,
+ CFG80211_RATE_STATS_STOP,
+};
+
+/**
* struct cfg80211_ops - backend description for wireless configuration
*
* This struct is registered by fullmac card drivers and/or wireless stacks
@@ -2410,6 +2425,9 @@ struct cfg80211_qos_map {
* and returning to the base channel for communication with the AP.
* @tdls_cancel_channel_switch: Stop channel-switching with a TDLS peer. Both
* peers must be on the base channel when the call completes.
+ *
+ * @rate_stats: rate statistics operation - if supported all operations must be
+ * supported, see &enum cfg80211_rate_stats_ops.
*/
struct cfg80211_ops {
int (*suspend)(struct wiphy *wiphy, struct cfg80211_wowlan *wow);
@@ -2673,6 +2691,9 @@ struct cfg80211_ops {
void (*tdls_cancel_channel_switch)(struct wiphy *wiphy,
struct net_device *dev,
const u8 *addr);
+
+ void (*rate_stats)(struct wiphy *wiphy,
+ enum cfg80211_rate_stats_ops op);
};

/*
@@ -5094,6 +5115,28 @@ wiphy_ext_feature_isset(struct wiphy *wiphy,
return (ft_byte & BIT(ftidx % 8)) != 0;
}

+/* rate statistics */
+struct cfg80211_rate_stats {
+ struct rate_info rate;
+ struct cfg80211_tid_stats stats;
+};
+
+/**
+ * cfg80211_report_rate_stats - report rate statistics for a station
+ * @wiphy: the wiphy that's reporting the data
+ * @wdev: the virtual interface the data is reported for
+ * @addr: the station MAC address
+ * @n_stats: length of statistics array
+ * @stats: array of per-rate statistics
+ * @gfp: allocation flags
+ *
+ * Note that it is valid to call this function multiple times even for the
+ * same station, if the data isn't actually stored in an array.
+ */
+void cfg80211_report_rate_stats(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const u8 *addr, unsigned int n_stats,
+ struct cfg80211_rate_stats *stats, gfp_t gfp);
+
/* ethtool helper */
void cfg80211_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info);

diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index f52797a90816..d03e3caef505 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -35,6 +35,7 @@
#define NL80211_MULTICAST_GROUP_MLME "mlme"
#define NL80211_MULTICAST_GROUP_VENDOR "vendor"
#define NL80211_MULTICAST_GROUP_TESTMODE "testmode"
+#define NL80211_MULTICAST_GROUP_RATESTATS "rate-stats"

/**
* DOC: Station handling
@@ -796,6 +797,10 @@
* as an event to indicate changes for devices with wiphy-specific regdom
* management.
*
+ * @NL80211_CMD_GET_RATE_STATISTICS: This command can be used to trigger the
+ * rate statistics dump to userspace, which also clears the data in the
+ * kernel (driver). See the "per-rate statistics" documentation section.
+ *
* @NL80211_CMD_MAX: highest used command number
* @__NL80211_CMD_AFTER_LAST: internal use
*/
@@ -982,6 +987,8 @@ enum nl80211_commands {

NL80211_CMD_WIPHY_REG_CHANGE,

+ NL80211_CMD_GET_RATE_STATISTICS,
+
/* add new commands above here */

/* used to define NL80211_CMD_MAX below */
@@ -1735,6 +1742,9 @@ enum nl80211_commands {
* should be contained in the result as the sum of the respective counters
* over all channels.
*
+ * @NL80211_ATTR_RATE_STATS: per-rate statistics container attribute, this is
+ * contains the attributes from &enum nl80211_rate_stats.
+ *
* @NUM_NL80211_ATTR: total number of nl80211_attrs available
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
@@ -2098,6 +2108,8 @@ enum nl80211_attrs {

NL80211_ATTR_SURVEY_RADIO_STATS,

+ NL80211_ATTR_RATE_STATS,
+
/* add attributes here, update the policy in nl80211.c */

__NL80211_ATTR_AFTER_LAST,
@@ -4318,10 +4330,13 @@ enum nl80211_feature_flags {
/**
* enum nl80211_ext_feature_index - bit index of extended features.
*
+ * @NL80211_EXT_FEATURE_RATE_STATS: This device supports the per-rate
+ * statistics subscription API.
* @NUM_NL80211_EXT_FEATURES: number of extended features.
* @MAX_NL80211_EXT_FEATURES: highest extended feature index.
*/
enum nl80211_ext_feature_index {
+ NL80211_EXT_FEATURE_RATE_STATS,

/* add new features before the definition below */
NUM_NL80211_EXT_FEATURES,
@@ -4547,4 +4562,65 @@ enum nl80211_tdls_peer_capability {
NL80211_TDLS_PEER_WMM = 1<<2,
};

+/**
+ * DOC: per-rate statistics
+ *
+ * The nl80211 API provides a way to subscribe to per-bitrate statistics. Since
+ * there are many bitrates it isn't always desirable to keep statistics for all
+ * of the rates in the kernel. As a consequence, the API allows the drivers to
+ * dump the information to userspace and reset their internal values. As it can
+ * also be expensive to keep the counters, this is only done when subscribers
+ * exist.
+ *
+ * To use this facility, a userspace client must subscribe to the data using the
+ * rate statistics multicast group (%NL80211_MULTICAST_GROUP_RATESTATS). This is
+ * used by the kernel to start data collection. If, at this point, other clients
+ * exist, those are also sent the current statistics in order to allow the new
+ * client to collect only the data obtained while subscribed.
+ *
+ * While subscribed, the client must listen for %NL80211_CMD_GET_RATE_STATISTICS
+ * events sent to the subscribed socket, and accumulate data retrieved in them.
+ * Every time such an event is sent by the kernel, the in-kernel data is also
+ * cleared. Therefore, to achieve data collection over longer periods of time,
+ * the subscribers must accumulate data. No guarantees are made about how long
+ * the kernel will collect data, but (as an implementation guideline) the data
+ * shouldn't be sent out frequently, and only while traffic is keeping the CPU
+ * busy anyway (i.e. it is recommended to not use timers in drivers supporting
+ * this facility.)
+ *
+ * In order to obtain a sample or clear the statistics at a given point in time,
+ * the %NL80211_CMD_GET_RATE_STATISTICS command can be used. This command can
+ * be called by any nl80211 client (even non-subscribers) and causes the kernel
+ * to send out and clear (atomically) the currently accumulated data to all of
+ * the subscribers.
+ *
+ * Note that the data sent out in each notification contains only some data for
+ * a single station (identified by the interface index and the station's MAC
+ * address.) It is therefore expected that multiple messages will be received
+ * by an application, possibly even multiple messages for the same station and
+ * the same rate (e.g. for separate RX and TX counters), and subscribers need
+ * to ensure that their socket buffers are big enough to retrieve all the data.
+ */
+
+/**
+ * enum nl80211_rate_stats - per-rate statistics container attributes
+ * @__NL80211_ATTR_RATE_STATS_INVALID: attribute number 0 is reserved
+ * @NL80211_ATTR_RATE_STATS_RATE: bitrate definition, nested attribute
+ * containing the attributes from &enum nl80211_rate_info.
+ * @NL80211_ATTR_RATE_STATS_STATS: statistics values, nested attribute
+ * containing the attributes from &enum nl80211_tid_stats (even
+ * though the statistics are not per TID)
+ * @NUM_NL80211_ATTR_RATE_STATS: number of attributes here
+ * @NL80211_ATTR_RATE_STATS_MAX: highest numbered attribute here
+ */
+enum nl80211_rate_stats {
+ __NL80211_ATTR_RATE_STATS_INVALID,
+ NL80211_ATTR_RATE_STATS_RATE,
+ NL80211_ATTR_RATE_STATS_STATS,
+
+ /* keep last */
+ NUM_NL80211_ATTR_RATE_STATS,
+ NL80211_ATTR_RATE_STATS_MAX = NUM_NL80211_ATTR_RATE_STATS - 1
+};
+
#endif /* __LINUX_NL80211_H */
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 456e4c38c279..79d914f794e9 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -562,6 +562,11 @@ int wiphy_register(struct wiphy *wiphy)
!rdev->ops->tdls_cancel_channel_switch)))
return -EINVAL;

+ if (WARN_ON(wiphy_ext_feature_isset(wiphy,
+ NL80211_EXT_FEATURE_RATE_STATS) &&
+ !rdev->ops->rate_stats))
+ return -EINVAL;
+
/*
* if a wiphy has unsupported modes for regulatory channel enforcement,
* opt-out of enforcement checking
@@ -724,6 +729,12 @@ int wiphy_register(struct wiphy *wiphy)
nl80211_send_reg_change_event(&request);
}

+ if (wiphy_ext_feature_isset(&rdev->wiphy,
+ NL80211_EXT_FEATURE_RATE_STATS) &&
+ genl_has_listeners(&nl80211_fam, wiphy_net(wiphy),
+ NL80211_MCGRP_RATESTATS))
+ rdev_rate_stats(rdev, CFG80211_RATE_STATS_START);
+
rdev->wiphy.registered = true;
rtnl_unlock();

@@ -773,6 +784,12 @@ void wiphy_unregister(struct wiphy *wiphy)
rfkill_unregister(rdev->rfkill);

rtnl_lock();
+ if (wiphy_ext_feature_isset(&rdev->wiphy,
+ NL80211_EXT_FEATURE_RATE_STATS) &&
+ genl_has_listeners(&nl80211_fam, wiphy_net(wiphy),
+ NL80211_MCGRP_RATESTATS))
+ rdev_rate_stats(rdev, CFG80211_RATE_STATS_STOP);
+
nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY);
rdev->wiphy.registered = false;

diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 8e56eeb583aa..b907fd21055f 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -35,9 +35,11 @@ static int nl80211_pre_doit(const struct genl_ops *ops, struct sk_buff *skb,
struct genl_info *info);
static void nl80211_post_doit(const struct genl_ops *ops, struct sk_buff *skb,
struct genl_info *info);
+static int nl80211_mcast_bind(struct net *net, int group);
+static void nl80211_mcast_unbind(struct net *net, int group);

/* the netlink family */
-static struct genl_family nl80211_fam = {
+struct genl_family nl80211_fam = {
.id = GENL_ID_GENERATE, /* don't bother with a hardcoded ID */
.name = NL80211_GENL_NAME, /* have users key off the name instead */
.hdrsize = 0, /* no private header */
@@ -46,24 +48,20 @@ static struct genl_family nl80211_fam = {
.netnsok = true,
.pre_doit = nl80211_pre_doit,
.post_doit = nl80211_post_doit,
+ .mcast_bind = nl80211_mcast_bind,
+ .mcast_unbind = nl80211_mcast_unbind,
};

/* multicast groups */
-enum nl80211_multicast_groups {
- NL80211_MCGRP_CONFIG,
- NL80211_MCGRP_SCAN,
- NL80211_MCGRP_REGULATORY,
- NL80211_MCGRP_MLME,
- NL80211_MCGRP_VENDOR,
- NL80211_MCGRP_TESTMODE /* keep last - ifdef! */
-};
-
static const struct genl_multicast_group nl80211_mcgrps[] = {
[NL80211_MCGRP_CONFIG] = { .name = NL80211_MULTICAST_GROUP_CONFIG },
[NL80211_MCGRP_SCAN] = { .name = NL80211_MULTICAST_GROUP_SCAN },
[NL80211_MCGRP_REGULATORY] = { .name = NL80211_MULTICAST_GROUP_REG },
[NL80211_MCGRP_MLME] = { .name = NL80211_MULTICAST_GROUP_MLME },
[NL80211_MCGRP_VENDOR] = { .name = NL80211_MULTICAST_GROUP_VENDOR },
+ [NL80211_MCGRP_RATESTATS] = {
+ .name = NL80211_MULTICAST_GROUP_RATESTATS
+ },
#ifdef CONFIG_NL80211_TESTMODE
[NL80211_MCGRP_TESTMODE] = { .name = NL80211_MULTICAST_GROUP_TESTMODE }
#endif
@@ -3668,6 +3666,33 @@ static bool nl80211_put_signal(struct sk_buff *msg, u8 mask, s8 *signal,
return true;
}

+static bool nl80211_put_tidstats(struct sk_buff *msg,
+ struct cfg80211_tid_stats *tidstats,
+ u32 attr)
+{
+ struct nlattr *tidattr = nla_nest_start(msg, attr);
+
+ if (!tidattr)
+ return false;
+
+#define PUT_TIDVAL(attr, memb, type) do { \
+ if (tidstats->filled & BIT(NL80211_TID_STATS_ ## attr) && \
+ nla_put_ ## type(msg, NL80211_TID_STATS_ ## attr, \
+ tidstats->memb)) \
+ return false; \
+ } while (0)
+
+ PUT_TIDVAL(RX_MSDU, rx_msdu, u64);
+ PUT_TIDVAL(TX_MSDU, tx_msdu, u64);
+ PUT_TIDVAL(TX_MSDU_RETRIES, tx_msdu_retries, u64);
+ PUT_TIDVAL(TX_MSDU_FAILED, tx_msdu_failed, u64);
+
+#undef PUT_TIDVAL
+ nla_nest_end(msg, tidattr);
+
+ return true;
+}
+
static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid,
u32 seq, int flags,
struct cfg80211_registered_device *rdev,
@@ -3801,31 +3826,14 @@ static int nl80211_send_station(struct sk_buff *msg, u32 cmd, u32 portid,

for (tid = 0; tid < IEEE80211_NUM_TIDS + 1; tid++) {
struct cfg80211_tid_stats *tidstats;
- struct nlattr *tidattr;

tidstats = &sinfo->pertid[tid];

if (!tidstats->filled)
continue;

- tidattr = nla_nest_start(msg, tid + 1);
- if (!tidattr)
+ if (!nl80211_put_tidstats(msg, tidstats, tid + 1))
goto nla_put_failure;
-
-#define PUT_TIDVAL(attr, memb, type) do { \
- if (tidstats->filled & BIT(NL80211_TID_STATS_ ## attr) && \
- nla_put_ ## type(msg, NL80211_TID_STATS_ ## attr, \
- tidstats->memb)) \
- goto nla_put_failure; \
- } while (0)
-
- PUT_TIDVAL(RX_MSDU, rx_msdu, u64);
- PUT_TIDVAL(TX_MSDU, tx_msdu, u64);
- PUT_TIDVAL(TX_MSDU_RETRIES, tx_msdu_retries, u64);
- PUT_TIDVAL(TX_MSDU_FAILED, tx_msdu_failed, u64);
-
-#undef PUT_TIDVAL
- nla_nest_end(msg, tidattr);
}

nla_nest_end(msg, tidsattr);
@@ -10135,6 +10143,28 @@ static int nl80211_tdls_cancel_channel_switch(struct sk_buff *skb,
return 0;
}

+static int nl80211_get_rate_stats(struct sk_buff *skb, struct genl_info *info)
+{
+ struct cfg80211_registered_device *rdev;
+
+ if (!genl_has_listeners(&nl80211_fam, genl_info_net(info),
+ NL80211_MCGRP_RATESTATS))
+ return -ENODATA;
+
+ list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
+ if (wiphy_net(&rdev->wiphy) != genl_info_net(info))
+ continue;
+
+ if (!wiphy_ext_feature_isset(&rdev->wiphy,
+ NL80211_EXT_FEATURE_RATE_STATS))
+ continue;
+
+ rdev_rate_stats(rdev, CFG80211_RATE_STATS_DUMP);
+ }
+
+ return 0;
+}
+
#define NL80211_FLAG_NEED_WIPHY 0x01
#define NL80211_FLAG_NEED_NETDEV 0x02
#define NL80211_FLAG_NEED_RTNL 0x04
@@ -10245,6 +10275,56 @@ static void nl80211_post_doit(const struct genl_ops *ops, struct sk_buff *skb,
}
}

+static void nl80211_do_rate_stats(struct net *net,
+ enum cfg80211_rate_stats_ops op)
+{
+ struct cfg80211_registered_device *rdev;
+
+ rtnl_lock();
+
+ list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
+ if (wiphy_net(&rdev->wiphy) != net)
+ continue;
+
+ if (!wiphy_ext_feature_isset(&rdev->wiphy,
+ NL80211_EXT_FEATURE_RATE_STATS))
+ continue;
+
+ rdev_rate_stats(rdev, op);
+ }
+
+ rtnl_unlock();
+}
+
+static int nl80211_mcast_bind(struct net *net, int group)
+{
+ enum cfg80211_rate_stats_ops op;
+
+ switch (group) {
+ case NL80211_MCGRP_RATESTATS:
+ if (!genl_has_listeners(&nl80211_fam, net,
+ NL80211_MCGRP_RATESTATS))
+ op = CFG80211_RATE_STATS_START;
+ else
+ op = CFG80211_RATE_STATS_DUMP;
+ nl80211_do_rate_stats(net, op);
+ break;
+ }
+
+ return 0;
+}
+
+static void nl80211_mcast_unbind(struct net *net, int group)
+{
+ switch (group) {
+ case NL80211_MCGRP_RATESTATS:
+ if (!genl_has_listeners(&nl80211_fam, net,
+ NL80211_MCGRP_RATESTATS))
+ nl80211_do_rate_stats(net, CFG80211_RATE_STATS_STOP);
+ break;
+ }
+}
+
static const struct genl_ops nl80211_ops[] = {
{
.cmd = NL80211_CMD_GET_WIPHY,
@@ -10950,6 +11030,13 @@ static const struct genl_ops nl80211_ops[] = {
.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
+ {
+ .cmd = NL80211_CMD_GET_RATE_STATISTICS,
+ .doit = nl80211_get_rate_stats,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL80211_FLAG_NEED_RTNL,
+ },
};

/* notification functions */
@@ -12838,6 +12925,71 @@ void cfg80211_crit_proto_stopped(struct wireless_dev *wdev, gfp_t gfp)
}
EXPORT_SYMBOL(cfg80211_crit_proto_stopped);

+void cfg80211_report_rate_stats(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const u8 *addr, unsigned int n_stats,
+ struct cfg80211_rate_stats *stats, gfp_t gfp)
+{
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
+ unsigned int i;
+
+ if (!genl_has_listeners(&nl80211_fam, wiphy_net(wiphy),
+ NL80211_MCGRP_RATESTATS))
+ return;
+
+ for (i = 0; i < n_stats; i++) {
+ struct sk_buff *msg;
+ void *hdr;
+ struct nlattr *attr_all;
+
+ if (!stats[i].stats.filled)
+ continue;
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
+ if (!msg)
+ continue;
+
+ hdr = nl80211hdr_put(msg, 0, 0, 0,
+ NL80211_CMD_GET_RATE_STATISTICS);
+ if (!hdr)
+ goto nla_put_failure;
+
+ if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
+ nla_put_u64(msg, NL80211_ATTR_WDEV, wdev_id(wdev)))
+ goto nla_put_failure;
+
+ if (wdev->netdev &&
+ nla_put_u32(msg, NL80211_ATTR_IFINDEX,
+ wdev->netdev->ifindex))
+ goto nla_put_failure;
+
+ if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, addr))
+ goto nla_put_failure;
+
+ attr_all = nla_nest_start(msg, NL80211_ATTR_RATE_STATS);
+ if (!attr_all)
+ goto nla_put_failure;
+
+ if (!nl80211_put_sta_rate(msg, &stats[i].rate,
+ NL80211_ATTR_RATE_STATS_RATE))
+ goto nla_put_failure;
+
+ if (!nl80211_put_tidstats(msg, &stats[i].stats,
+ NL80211_ATTR_RATE_STATS_STATS))
+ goto nla_put_failure;
+
+ nla_nest_end(msg, attr_all);
+
+ genlmsg_end(msg, hdr);
+
+ genlmsg_multicast_netns(&nl80211_fam, wiphy_net(wiphy), msg, 0,
+ NL80211_MCGRP_RATESTATS, GFP_KERNEL);
+ continue;
+ nla_put_failure:
+ nlmsg_free(msg);
+ }
+}
+EXPORT_SYMBOL_GPL(cfg80211_report_rate_stats);
+
void nl80211_send_ap_stopped(struct wireless_dev *wdev)
{
struct wiphy *wiphy = wdev->wiphy;
diff --git a/net/wireless/nl80211.h b/net/wireless/nl80211.h
index 84d4edf1d545..39c87e396595 100644
--- a/net/wireless/nl80211.h
+++ b/net/wireless/nl80211.h
@@ -3,6 +3,21 @@

#include "core.h"

+/* netlink family */
+extern struct genl_family nl80211_fam;
+
+/* multicast groups */
+enum nl80211_multicast_groups {
+ NL80211_MCGRP_CONFIG,
+ NL80211_MCGRP_SCAN,
+ NL80211_MCGRP_REGULATORY,
+ NL80211_MCGRP_MLME,
+ NL80211_MCGRP_VENDOR,
+ NL80211_MCGRP_RATESTATS,
+ NL80211_MCGRP_TESTMODE /* keep last - ifdef! */
+};
+
+
int nl80211_init(void);
void nl80211_exit(void);
void nl80211_notify_wiphy(struct cfg80211_registered_device *rdev,
diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h
index 35cfb7134bdb..67c7e329489c 100644
--- a/net/wireless/rdev-ops.h
+++ b/net/wireless/rdev-ops.h
@@ -1017,4 +1017,14 @@ rdev_tdls_cancel_channel_switch(struct cfg80211_registered_device *rdev,
trace_rdev_return_void(&rdev->wiphy);
}

+static inline void
+rdev_rate_stats(struct cfg80211_registered_device *rdev,
+ enum cfg80211_rate_stats_ops op)
+{
+ trace_rdev_rate_stats(&rdev->wiphy, op);
+ if (rdev->ops->rate_stats)
+ rdev->ops->rate_stats(&rdev->wiphy, op);
+ trace_rdev_return_void(&rdev->wiphy);
+}
+
#endif /* __CFG80211_RDEV_OPS */
diff --git a/net/wireless/trace.h b/net/wireless/trace.h
index b17b3692f8c2..b59a9c6231f5 100644
--- a/net/wireless/trace.h
+++ b/net/wireless/trace.h
@@ -2077,6 +2077,20 @@ TRACE_EVENT(rdev_tdls_cancel_channel_switch,
WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(addr))
);

+TRACE_EVENT(rdev_rate_stats,
+ TP_PROTO(struct wiphy *wiphy, enum cfg80211_rate_stats_ops op),
+ TP_ARGS(wiphy, op),
+ TP_STRUCT__entry(
+ WIPHY_ENTRY
+ __field(u32, op)
+ ),
+ TP_fast_assign(
+ WIPHY_ASSIGN;
+ __entry->op = op;
+ ),
+ TP_printk(WIPHY_PR_FMT ", op=%d", WIPHY_PR_ARG, __entry->op)
+);
+
/*************************************************************
* cfg80211 exported functions traces *
*************************************************************/
--
2.1.4



2015-01-16 10:49:31

by Bjørn Mork

[permalink] [raw]
Subject: Re: [RFC 1/2] nl80211: support per-rate/per-station statistics

Johannes Berg <[email protected]> writes:

> +void cfg80211_report_rate_stats(struct wiphy *wiphy, struct wireless_dev *wdev,
> + const u8 *addr, unsigned int n_stats,
> + struct cfg80211_rate_stats *stats, gfp_t gfp);
> +
> /* ethtool helper */
> void cfg80211_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info);
>
> diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
> index f52797a90816..d03e3caef505 100644
> --- a/include/uapi/linux/nl80211.h
> +++ b/include/uapi/linux/nl80211.h
> @@ -35,6 +35,7 @@
> #define NL80211_MULTICAST_GROUP_MLME "mlme"
> #define NL80211_MULTICAST_GROUP_VENDOR "vendor"
> #define NL80211_MULTICAST_GROUP_TESTMODE "testmode"
> +#define NL80211_MULTICAST_GROUP_RATESTATS "rate-stats"
>
> /**
> * DOC: Station handling
> @@ -796,6 +797,10 @@
> * as an event to indicate changes for devices with wiphy-specific regdom
> * management.
> *
> + * @NL80211_CMD_GET_RATE_STATISTICS: This command can be used to trigger the

So in a few lines we have all these phrases describing the same feature
in different contexts (3 of which are different even after ignoring
casing and _/- translation):

rate_stats
RATESTATS
rate-stats
RATE_STATISTICS

How about sticking to one phrase to avoid having to look these up every
time you use them?

I don't care too much about which one it would be. But if I were to
choose, I would have gone for the shortest one: RATESTATS. It also has
the advantage that you don't need the _/- translation, which avoids
another possible confusion.


Bjørn

2015-01-16 10:50:35

by Johannes Berg

[permalink] [raw]
Subject: Re: [RFC 1/2] nl80211: support per-rate/per-station statistics

On Fri, 2015-01-16 at 11:49 +0100, Bjørn Mork wrote:
> Johannes Berg <[email protected]> writes:
>
> > +void cfg80211_report_rate_stats(struct wiphy *wiphy, struct wireless_dev *wdev,
> > + const u8 *addr, unsigned int n_stats,
> > + struct cfg80211_rate_stats *stats, gfp_t gfp);
> > +
> > /* ethtool helper */
> > void cfg80211_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info);
> >
> > diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
> > index f52797a90816..d03e3caef505 100644
> > --- a/include/uapi/linux/nl80211.h
> > +++ b/include/uapi/linux/nl80211.h
> > @@ -35,6 +35,7 @@
> > #define NL80211_MULTICAST_GROUP_MLME "mlme"
> > #define NL80211_MULTICAST_GROUP_VENDOR "vendor"
> > #define NL80211_MULTICAST_GROUP_TESTMODE "testmode"
> > +#define NL80211_MULTICAST_GROUP_RATESTATS "rate-stats"
> >
> > /**
> > * DOC: Station handling
> > @@ -796,6 +797,10 @@
> > * as an event to indicate changes for devices with wiphy-specific regdom
> > * management.
> > *
> > + * @NL80211_CMD_GET_RATE_STATISTICS: This command can be used to trigger the
>
> So in a few lines we have all these phrases describing the same feature
> in different contexts (3 of which are different even after ignoring
> casing and _/- translation):
>
> rate_stats
> RATESTATS
> rate-stats
> RATE_STATISTICS
>
> How about sticking to one phrase to avoid having to look these up every
> time you use them?

Heh, that's a good point. "ratestats" is good with me - I guess that's
pretty much a consequence of working on the code for such a long
time :-)

johannes


2015-01-20 04:47:31

by Sujith Manoharan

[permalink] [raw]
Subject: Re: [RFC 2/2] mac80211: support RX rate statistics

Johannes Berg wrote:
> Not right now - and there's practically no space left in the rate u16 to
> store it.
>
> Eyal also commented that perhaps not just antennas but also
> STBC/LDPC/beamforming should be tracked.
>
> We can achieve this by increasing the size of the rate field to u32, but
> then we also have far more combinations so probably need to increase the
> size of the buffer from 36 entries to more since otherwise we just end
> up sending the data out to userspace too frequently? Antennas for
> example could change frequently for single stream rates.
>
> This capability right now is mostly intended for Android Lollipop's
> statistics requirements, where the rate only matters.

Ok.

> If others want to use it for more, I'm certainly not averse to adding
> it, but it'd also mean adding more userspace API (where it might be a
> bit strange to add "antennas" to the "rate" since that doesn't impact
> the actual rate?)

Having a well-defined API to export statistics seems useful, but
I guess that can be added later.

Sujith

2015-01-17 01:51:40

by Sujith Manoharan

[permalink] [raw]
Subject: Re: [RFC 2/2] mac80211: support RX rate statistics

Johannes Berg wrote:
> From: Johannes Berg <[email protected]>
>
> Add support for RX rate statistics to mac80211. This tracks the number
> of MSDUs received per rate, if rate statistics are enabled.
>
> As the number of rates is very high (as explained in the commit log of
> the cfg80211 patch) mac80211 tracks at most 36 different rates at once.
> This is done with a very simple hash table format (3 * index) array to
> track three different NSS/SGI/bw combinations, plus a few more entries
> acting as "escape" when all three different combinations are filled.
>
> Counters are limited to 16-bit to reduce memory consumption and when a
> limit of 64000 is reached the process to send them out to userspace is
> scheduled. The same happens when the escape entries need to be used.
>
> The data structure itself is stored for each station, accessible under
> RCU, and sending them to userspace is combined with freeing so that no
> RCU-accessor (really only the synchronized RX path anyway though) can
> still see them when they're transmitted out. This ensures a consistent
> view is transmitted right before being discarded; it also ensures that
> the RX path isn't blocked by any configuration paths for this.
>
> Note that TX statistics (tx, retries, failures) aren't covered. Those
> need tighter integration with the driver or rate scaling as not all
> drivers are able to fully encode the necessary information in the TX
> status data due to the size limit there.

Nice. :)

We were doing simple RX statistics collection inside ath9k
(CONFIG_ATH9K_STATION_STATISTICS), which can be removed once this goes
in. Is there any way a driver can pass custom data, like per-frame
antenna details ?

Sujith

2015-01-20 11:06:37

by Johannes Berg

[permalink] [raw]
Subject: Re: [RFC 2/2] mac80211: support RX rate statistics

On Tue, 2015-01-20 at 10:20 +0530, Sujith Manoharan wrote:

> > If others want to use it for more, I'm certainly not averse to adding
> > it, but it'd also mean adding more userspace API (where it might be a
> > bit strange to add "antennas" to the "rate" since that doesn't impact
> > the actual rate?)
>
> Having a well-defined API to export statistics seems useful, but
> I guess that can be added later.

Yeah I thought about this - I think we can add more attributes to the
userspace API later as userspace for this must always be prepared to get
multiple messages for what it considers the same rate.

I'm going to have to focus on the TX statistics next, so won't be
working on extending this.

johannes


2015-01-19 09:55:26

by Johannes Berg

[permalink] [raw]
Subject: Re: [RFC 2/2] mac80211: support RX rate statistics

On Sat, 2015-01-17 at 07:24 +0530, Sujith Manoharan wrote:

> We were doing simple RX statistics collection inside ath9k
> (CONFIG_ATH9K_STATION_STATISTICS), which can be removed once this goes
> in. Is there any way a driver can pass custom data, like per-frame
> antenna details ?

Not right now - and there's practically no space left in the rate u16 to
store it.

Eyal also commented that perhaps not just antennas but also
STBC/LDPC/beamforming should be tracked.

We can achieve this by increasing the size of the rate field to u32, but
then we also have far more combinations so probably need to increase the
size of the buffer from 36 entries to more since otherwise we just end
up sending the data out to userspace too frequently? Antennas for
example could change frequently for single stream rates.

This capability right now is mostly intended for Android Lollipop's
statistics requirements, where the rate only matters.

If others want to use it for more, I'm certainly not averse to adding
it, but it'd also mean adding more userspace API (where it might be a
bit strange to add "antennas" to the "rate" since that doesn't impact
the actual rate?)

johannes


2015-01-16 09:48:47

by Johannes Berg

[permalink] [raw]
Subject: [RFC 2/2] mac80211: support RX rate statistics

From: Johannes Berg <[email protected]>

Add support for RX rate statistics to mac80211. This tracks the number
of MSDUs received per rate, if rate statistics are enabled.

As the number of rates is very high (as explained in the commit log of
the cfg80211 patch) mac80211 tracks at most 36 different rates at once.
This is done with a very simple hash table format (3 * index) array to
track three different NSS/SGI/bw combinations, plus a few more entries
acting as "escape" when all three different combinations are filled.

Counters are limited to 16-bit to reduce memory consumption and when a
limit of 64000 is reached the process to send them out to userspace is
scheduled. The same happens when the escape entries need to be used.

The data structure itself is stored for each station, accessible under
RCU, and sending them to userspace is combined with freeing so that no
RCU-accessor (really only the synchronized RX path anyway though) can
still see them when they're transmitted out. This ensures a consistent
view is transmitted right before being discarded; it also ensures that
the RX path isn't blocked by any configuration paths for this.

Note that TX statistics (tx, retries, failures) aren't covered. Those
need tighter integration with the driver or rate scaling as not all
drivers are able to fully encode the necessary information in the TX
status data due to the size limit there.

Signed-off-by: Johannes Berg <[email protected]>
---
net/mac80211/cfg.c | 41 ++++++++++++++
net/mac80211/ieee80211_i.h | 2 +
net/mac80211/main.c | 12 ++++
net/mac80211/rx.c | 57 +++++++++++++++++++
net/mac80211/sta_info.c | 135 +++++++++++++++++++++++++++++++++++++++++++++
net/mac80211/sta_info.h | 59 ++++++++++++++++++++
6 files changed, 306 insertions(+)

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index ff090ef1ea2c..23a104ebea93 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -3690,6 +3690,46 @@ static int ieee80211_del_tx_ts(struct wiphy *wiphy, struct net_device *dev,
return -ENOENT;
}

+static void ieee80211_rate_stats(struct wiphy *wiphy,
+ enum cfg80211_rate_stats_ops op)
+{
+ struct ieee80211_local *local = wiphy_priv(wiphy);
+ struct sta_info *sta;
+ struct ieee80211_sta_rate_stats *stats, *tmp;
+ LIST_HEAD(list);
+
+ mutex_lock(&local->sta_mtx);
+ switch (op) {
+ case CFG80211_RATE_STATS_START:
+ local->rate_stats_active = true;
+ list_for_each_entry(sta, &local->sta_list, list)
+ ieee80211_sta_start_rate_stats(sta);
+ /* list stays empty */
+ break;
+ case CFG80211_RATE_STATS_DUMP:
+ list_for_each_entry(sta, &local->sta_list, list) {
+ stats = ieee80211_sta_reset_rate_stats(sta);
+ if (stats)
+ list_add_tail(&stats->list, &list);
+ }
+ break;
+ case CFG80211_RATE_STATS_STOP:
+ local->rate_stats_active = false;
+ list_for_each_entry(sta, &local->sta_list, list) {
+ stats = ieee80211_sta_stop_rate_stats(sta);
+ if (stats)
+ list_add_tail(&stats->list, &list);
+ }
+ break;
+ }
+ mutex_unlock(&local->sta_mtx);
+
+ if (!list_empty(&list))
+ synchronize_rcu();
+ list_for_each_entry_safe(stats, tmp, &list, list)
+ ieee80211_sta_free_rate_stats(stats, true);
+}
+
const struct cfg80211_ops mac80211_config_ops = {
.add_virtual_intf = ieee80211_add_iface,
.del_virtual_intf = ieee80211_del_iface,
@@ -3774,4 +3814,5 @@ const struct cfg80211_ops mac80211_config_ops = {
.set_ap_chanwidth = ieee80211_set_ap_chanwidth,
.add_tx_ts = ieee80211_add_tx_ts,
.del_tx_ts = ieee80211_del_tx_ts,
+ .rate_stats = ieee80211_rate_stats,
};
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 156ea79e0157..59f1b2ecad16 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1126,6 +1126,8 @@ struct ieee80211_local {

bool use_chanctx;

+ bool rate_stats_active;
+
/* protects the aggregated multicast list and filter calls */
spinlock_t filter_lock;

diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index d9ce33663c73..d26fe28e07f2 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -545,6 +545,8 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
NL80211_FEATURE_MAC_ON_CREATE |
NL80211_FEATURE_USERSPACE_MPM;

+ wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_RATE_STATS);
+
if (!ops->hw_scan)
wiphy->features |= NL80211_FEATURE_LOW_PRIORITY_SCAN |
NL80211_FEATURE_AP_SCAN;
@@ -859,6 +861,16 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
/* TODO: consider VHT for RX chains, hopefully it's the same */
}

+ /* if the HW has more than 12 legacy rates, some assumptions in the
+ * data structures break - in this case don't allow the rate-stats
+ * feature flag.
+ */
+ if (WARN_ON(wiphy_ext_feature_isset(hw->wiphy,
+ NL80211_EXT_FEATURE_RATE_STATS) &&
+ max_bitrates > 12))
+ hw->wiphy->ext_features[NL80211_EXT_FEATURE_RATE_STATS / 8] &=
+ ~BIT(NL80211_EXT_FEATURE_RATE_STATS % 8);
+
/* if low-level driver supports AP, we also support VLAN */
if (local->hw.wiphy->interface_modes & BIT(NL80211_IFTYPE_AP)) {
hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP_VLAN);
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 3d79d498e7f6..8188ecdeaa8d 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -2292,6 +2292,56 @@ ieee80211_rx_h_mesh_fwding(struct ieee80211_rx_data *rx)
}
#endif

+static void noinline
+ieee80211_rx_handle_rate_stats(struct ieee80211_local *local,
+ struct ieee80211_rx_status *status,
+ struct ieee80211_sta_rate_stats *rstats)
+{
+ u16 rate = sta_rate_stats_encode_rate(status);
+ unsigned int start;
+ int i;
+
+ start = status->rate_idx;
+ /* for HT - use only the base index */
+ if (status->flag & RX_FLAG_HT)
+ start &= 0xF;
+ for (i = start; i < start + 3; i++) {
+ if (rstats->entries[i].rate == rate ||
+ rstats->entries[i].rate == STA_RATE_STATS_RATE_INVALID) {
+ if (rstats->entries[i].rate ==
+ STA_RATE_STATS_RATE_INVALID)
+ rstats->entries[i].rate = rate;
+ if (rstats->entries[i].rx++ > 64000)
+ goto schedule_dump;
+ return;
+ }
+ }
+
+ /* The last six entries are only really used by legacy rates - so
+ * not that performance sensitive. For HT and VHT though, only the
+ * first 24 (3*8) or 30 (3*10) entries can be used, so try the last
+ * few in the list as "escape" entries. If any such entry is used
+ * then schedule dumping to userspace unconditionally to avoid going
+ * into this code path frequently.
+ */
+ for (i = ARRAY_SIZE(rstats->entries) - 1;
+ i > ARRAY_SIZE(rstats->entries) - 7; i--) {
+ if (rstats->entries[i].rate == rate ||
+ rstats->entries[i].rate == STA_RATE_STATS_RATE_INVALID) {
+ if (rstats->entries[i].rate ==
+ STA_RATE_STATS_RATE_INVALID)
+ rstats->entries[i].rate = rate;
+ rstats->entries[i].rx++;
+ break;
+ }
+ }
+
+ /* if we still didn't find anything, drop the stats for this packet */
+
+ schedule_dump:
+ ieee80211_queue_work(&local->hw, &rstats->dump_wk);
+}
+
static ieee80211_rx_result debug_noinline
ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
{
@@ -2310,12 +2360,19 @@ ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
return RX_DROP_MONITOR;

if (rx->sta) {
+ struct ieee80211_sta_rate_stats *rstats;
+
/* The security index has the same property as needed
* for the rx_msdu field, i.e. it is IEEE80211_NUM_TIDS
* for non-QoS-data frames. Here we know it's a data
* frame, so count MSDUs.
*/
rx->sta->rx_msdu[rx->security_idx]++;
+
+ rstats = rcu_dereference(rx->sta->rate_stats);
+ if (rstats)
+ ieee80211_rx_handle_rate_stats(local,
+ IEEE80211_SKB_RXCB(rx->skb), rstats);
}

/*
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 79383ef0c264..2ca35425d9ab 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -497,6 +497,9 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU)
local->sta_generation++;
smp_mb();

+ if (local->rate_stats_active)
+ ieee80211_sta_start_rate_stats(sta);
+
/* simplify things and don't accept BA sessions yet */
set_sta_flag(sta, WLAN_STA_BLOCK_BA);

@@ -882,6 +885,7 @@ static void __sta_info_destroy_part2(struct sta_info *sta)
struct ieee80211_local *local = sta->local;
struct ieee80211_sub_if_data *sdata = sta->sdata;
struct station_info sinfo = {};
+ struct ieee80211_sta_rate_stats *stats;
int ret;

/*
@@ -919,6 +923,10 @@ static void __sta_info_destroy_part2(struct sta_info *sta)

sta_dbg(sdata, "Removed STA %pM\n", sta->sta.addr);

+ stats = ieee80211_sta_stop_rate_stats(sta);
+ /* station was already unlinked and rcu sync'ed before getting here */
+ ieee80211_sta_free_rate_stats(stats, true);
+
sta_set_sinfo(sta, &sinfo);
cfg80211_del_sta_sinfo(sdata->dev, sta->sta.addr, &sinfo, GFP_KERNEL);

@@ -1950,3 +1958,130 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo)
sinfo->expected_throughput = thr;
}
}
+
+static void sta_rate_stats_decode_rate(u16 r, struct ieee80211_local *local,
+ struct rate_info *rinfo)
+{
+ rinfo->bw = (r & STA_RATE_STATS_RATE_BW_MASK) >>
+ STA_RATE_STATS_RATE_BW_SHIFT;
+
+ if (r & STA_RATE_STATS_RATE_VHT) {
+ rinfo->flags = RATE_INFO_FLAGS_VHT_MCS;
+ rinfo->mcs = r & 0xf;
+ rinfo->nss = (r & 0xf0) >> 4;
+ } else if (r & STA_RATE_STATS_RATE_HT) {
+ rinfo->flags = RATE_INFO_FLAGS_MCS;
+ rinfo->mcs = r & 0xff;
+ } else if (r & STA_RATE_STATS_RATE_LEGACY) {
+ struct ieee80211_supported_band *sband;
+ u16 brate;
+ unsigned int shift;
+
+ sband = local->hw.wiphy->bands[(r >> 4) & 0xf];
+ brate = sband->bitrates[r & 0xf].bitrate;
+ if (rinfo->bw == RATE_INFO_BW_5)
+ shift = 2;
+ else if (rinfo->bw == RATE_INFO_BW_10)
+ shift = 1;
+ else
+ shift = 0;
+ rinfo->legacy = DIV_ROUND_UP(brate, 1 << shift);
+ }
+
+ if (r & STA_RATE_STATS_RATE_SGI)
+ rinfo->flags |= RATE_INFO_FLAGS_SHORT_GI;
+}
+
+void ieee80211_sta_start_rate_stats(struct sta_info *sta)
+{
+ struct ieee80211_sta_rate_stats *stats;
+
+ stats = ieee80211_sta_reset_rate_stats(sta);
+
+ if (WARN_ON_ONCE(stats)) {
+ /* error case - be really slow but correct */
+ synchronize_rcu();
+ ieee80211_sta_free_rate_stats(stats, true);
+ }
+}
+
+static void ieee80211_sta_dump_rate_stats_wk(struct work_struct *wk)
+{
+ struct ieee80211_sta_rate_stats *stats, *stats2;
+
+ stats = container_of(wk, struct ieee80211_sta_rate_stats, dump_wk);
+
+ mutex_lock(&stats->sta->local->sta_mtx);
+ stats2 = ieee80211_sta_reset_rate_stats(stats->sta);
+ mutex_unlock(&stats->sta->local->sta_mtx);
+
+ WARN_ON_ONCE(stats != stats2);
+
+ synchronize_rcu();
+ ieee80211_sta_free_rate_stats(stats, false);
+}
+
+struct ieee80211_sta_rate_stats * __must_check
+ieee80211_sta_reset_rate_stats(struct sta_info *sta)
+{
+ struct ieee80211_sta_rate_stats *stats, *new;
+
+ stats = rcu_dereference_protected(sta->rate_stats,
+ lockdep_is_held(&sta->local->sta_mtx));
+
+ new = kzalloc(sizeof(*new), GFP_KERNEL);
+ new->sta = sta;
+ INIT_WORK(&new->dump_wk, ieee80211_sta_dump_rate_stats_wk);
+
+ rcu_assign_pointer(sta->rate_stats, new);
+
+ /* caller must wait for grace period and free data */
+ return stats;
+}
+
+struct ieee80211_sta_rate_stats * __must_check
+ieee80211_sta_stop_rate_stats(struct sta_info *sta)
+{
+ struct ieee80211_sta_rate_stats *stats;
+
+ stats = rcu_dereference_protected(sta->rate_stats,
+ lockdep_is_held(&sta->local->sta_mtx));
+
+ RCU_INIT_POINTER(sta->rate_stats, NULL);
+
+ /* caller must wait for grace period and free data */
+ return stats;
+}
+
+void ieee80211_sta_free_rate_stats(struct ieee80211_sta_rate_stats *stats,
+ bool flush)
+{
+ struct cfg80211_rate_stats report;
+ struct sta_info *sta;
+ int i;
+
+ if (!stats)
+ return;
+
+ if (flush && flush_work(&stats->dump_wk))
+ return;
+
+ sta = stats->sta;
+
+ for (i = 0; i < ARRAY_SIZE(stats->entries); i++) {
+ if (stats->entries[i].rate == STA_RATE_STATS_RATE_INVALID)
+ continue;
+
+ sta_rate_stats_decode_rate(stats->entries[i].rate,
+ stats->sta->local, &report.rate);
+
+ report.stats.filled = BIT(NL80211_TID_STATS_RX_MSDU);
+ report.stats.rx_msdu = stats->entries[i].rx;
+
+ cfg80211_report_rate_stats(sta->local->hw.wiphy,
+ &sta->sdata->wdev, sta->sta.addr,
+ 1, &report, GFP_KERNEL);
+ }
+
+ kfree(stats);
+}
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 925e68fe64c7..cd7c7b5111dd 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -354,6 +354,7 @@ struct ieee80211_tx_latency_stat {
* using IEEE80211_NUM_TID entry for non-QoS frames
* @rx_msdu: MSDUs received from this station, using IEEE80211_NUM_TID
* entry for non-QoS frames
+ * @rate_stats: rate statistics pointer (if enabled)
*/
struct sta_info {
/* General information, mostly static */
@@ -428,6 +429,7 @@ struct sta_info {
u64 tx_msdu_retries[IEEE80211_NUM_TIDS + 1];
u64 tx_msdu_failed[IEEE80211_NUM_TIDS + 1];
u64 rx_msdu[IEEE80211_NUM_TIDS + 1];
+ struct ieee80211_sta_rate_stats __rcu *rate_stats;

/*
* Aggregation information, locked with lock.
@@ -670,4 +672,61 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta);
void ieee80211_sta_ps_deliver_poll_response(struct sta_info *sta);
void ieee80211_sta_ps_deliver_uapsd(struct sta_info *sta);

+/* rate statistics */
+void ieee80211_sta_start_rate_stats(struct sta_info *sta);
+struct ieee80211_sta_rate_stats * __must_check
+ieee80211_sta_reset_rate_stats(struct sta_info *sta);
+struct ieee80211_sta_rate_stats * __must_check
+ieee80211_sta_stop_rate_stats(struct sta_info *sta);
+void ieee80211_sta_free_rate_stats(struct ieee80211_sta_rate_stats *stats,
+ bool flush);
+
+struct ieee80211_sta_rate_stats {
+ struct list_head list;
+ struct sta_info *sta;
+ struct work_struct dump_wk;
+ struct {
+#define STA_RATE_STATS_RATE_INVALID 0
+#define STA_RATE_STATS_RATE_VHT 0x8000
+#define STA_RATE_STATS_RATE_HT 0x4000
+#define STA_RATE_STATS_RATE_LEGACY 0x2000
+#define STA_RATE_STATS_RATE_SGI 0x1000
+#define STA_RATE_STATS_RATE_BW_SHIFT 9
+#define STA_RATE_STATS_RATE_BW_MASK (0x7 << STA_RATE_STATS_RATE_BW_SHIFT)
+ u16 rate;
+ u16 rx;
+ /* 3 * rate_idx/MCS - using a few from the end as escape */
+ } entries[36];
+};
+
+static inline u16 sta_rate_stats_encode_rate(struct ieee80211_rx_status *s)
+{
+ u16 r = s->rate_idx;
+
+ if (s->vht_flag & RX_VHT_FLAG_80MHZ)
+ r |= RATE_INFO_BW_80 << STA_RATE_STATS_RATE_BW_SHIFT;
+ else if (s->vht_flag & RX_VHT_FLAG_160MHZ)
+ r |= RATE_INFO_BW_160 << STA_RATE_STATS_RATE_BW_SHIFT;
+ else if (s->flag & RX_FLAG_40MHZ)
+ r |= RATE_INFO_BW_40 << STA_RATE_STATS_RATE_BW_SHIFT;
+ else if (s->flag & RX_FLAG_10MHZ)
+ r |= RATE_INFO_BW_10 << STA_RATE_STATS_RATE_BW_SHIFT;
+ else if (s->flag & RX_FLAG_5MHZ)
+ r |= RATE_INFO_BW_5 << STA_RATE_STATS_RATE_BW_SHIFT;
+ else
+ r |= RATE_INFO_BW_20 << STA_RATE_STATS_RATE_BW_SHIFT;
+
+ if (s->flag & RX_FLAG_SHORT_GI)
+ r |= STA_RATE_STATS_RATE_SGI;
+
+ if (s->flag & RX_FLAG_VHT)
+ r |= STA_RATE_STATS_RATE_VHT | (s->vht_nss << 4);
+ else if (s->flag & RX_FLAG_HT)
+ r |= STA_RATE_STATS_RATE_HT;
+ else
+ r |= STA_RATE_STATS_RATE_LEGACY | (s->band << 4);
+
+ return r;
+}
+
#endif /* STA_INFO_H */
--
2.1.4