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		     *
  *************************************************************/
