RX Statistics per rate, per station can be useful in understanding the quality of communication with our peers and can be helpful in evaluating the consistency at which a specific peer has been communicating in different MCS/BW/NSS after association at different time periods and in varied environments. This patch adds support for collecting the rx statistics from the kernel and providing it to the userspace. Signed-off-by: Sriram R <srirrama@xxxxxxxxxxxxxx> --- include/net/cfg80211.h | 25 +++++++ include/uapi/linux/nl80211.h | 50 +++++++++++++ net/wireless/nl80211.c | 172 +++++++++++++++++++++++++++++++++++++++++++ net/wireless/rdev-ops.h | 30 ++++++++ net/wireless/trace.h | 26 +++++++ 5 files changed, 303 insertions(+) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 8db6071..8bd818f 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1235,6 +1235,19 @@ struct station_info { s8 avg_ack_signal; }; +/** + * struct sta_rate_stats - per rate statistics of station + * + * @rate: encoded rate value + * @packets: Packet count received at this @rate + * @bytes: Total number of bytes received at this @rate + */ +struct sta_rate_stats { + u32 rate; + u32 packets; + u64 bytes; +}; + #if IS_ENABLED(CONFIG_CFG80211) /** * cfg80211_get_station - retrieve information about a given station @@ -3018,6 +3031,9 @@ struct cfg80211_external_auth_params { * * @tx_control_port: TX a control port frame (EAPoL). The noencrypt parameter * tells the driver that the frame should not be encrypted. + * + * @get_sta_rate_stats: get per-rate stats of the station identified by @mac + * @dump_sta_rate_stats: get per-rate stats dump of stations from index @idx */ struct cfg80211_ops { int (*suspend)(struct wiphy *wiphy, struct cfg80211_wowlan *wow); @@ -3323,6 +3339,15 @@ struct cfg80211_ops { const u8 *buf, size_t len, const u8 *dest, const __be16 proto, const bool noencrypt); + + int (*get_sta_rate_stats)(struct wiphy *wiphy, + struct net_device *dev, const u8 *mac, + struct sta_rate_stats **rate_stats_buf, + int *len); + int (*dump_sta_rate_stats)(struct wiphy *wiphy, + struct net_device *dev, int idx, u8 *mac, + struct sta_rate_stats **rate_stats_buf, + int *len); }; /* diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 83ed1dd..beeca81 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -1031,6 +1031,10 @@ * &NL80211_ATTR_CHANNEL_WIDTH,&NL80211_ATTR_NSS attributes with its * address(specified in &NL80211_ATTR_MAC). * + * @NL80211_CMD_GET_RATE_STATS: Get Per rate statistics (rx packets and bytes) + * for a station identified by %NL80211_ATTR_MAC on the interface + * identified by %NL80211_ATTR_IFINDEX. + * * @NL80211_CMD_MAX: highest used command number * @__NL80211_CMD_AFTER_LAST: internal use */ @@ -1243,6 +1247,8 @@ enum nl80211_commands { NL80211_CMD_CONTROL_PORT_FRAME, + NL80211_CMD_GET_RATE_STATS, + /* add new commands above here */ /* used to define NL80211_CMD_MAX below */ @@ -2236,6 +2242,10 @@ enum nl80211_commands { * @NL80211_ATTR_TXQ_QUANTUM: TXQ scheduler quantum (bytes). Number of bytes * a flow is assigned on each round of the DRR scheduler. * + * @NL80211_ATTR_RATE_STATS: Indicates the presence of per-rate stats holding + * the info of packets and bytes received per rate identified by + * the attributes in @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 @@ -2675,6 +2685,8 @@ enum nl80211_attrs { NL80211_ATTR_TXQ_MEMORY_LIMIT, NL80211_ATTR_TXQ_QUANTUM, + NL80211_ATTR_RATE_STATS, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -3114,6 +3126,44 @@ enum nl80211_txq_stats { }; /** + * DOC: per-rate per-station statistics + * The nl80211 %NL80211_CMD_GET_RATE_STATS command provides an interface to + * get the statistics of number of packets and bytes received per rate + * per station. + * The kernel will start collecting this statistics only when it is required + * by the userspace application and rate statistics needs to be enabled for + * this purpose(TODO: Add comments on interface used to enable rate-stats) + * Once enabled, the packet (and corresponding bytes) count received at the + * station are recorded per rate on each rx and the userspace application + * can get the current stats information using the %NL80211_CMD_GET_RATE_STATS + * command. + * Note that the rate field which is provided by the statistics is a encoded + * value containing the information on VHT/HT/Legacy,BW,NSS,MCS,GI/SGI and this + * needs to be extracted/decoded by the userspace application. + */ + +/** + * enum nl80211_rate_stats - per-rate stats attributes + * @__NL80211_RATE_STATS_INVALID :attribute number 0 is reserved + * @NL80211_ATTR_RATE_STATS_RATE :identifier for the encoded rate field. + * @NL80211_ATTR_RATE_STATS_RX_PACKETS: Number of packets which are received + * per @rate for this specific station identified by @NL80211_ATTR_MAC. + * @NL80211_ATTR_RATE_STATS_RX_BYTES: Number of bytes which are received + * per @rate for this specific station identified by @NL80211_ATTR_MAC. + * @NUM_NL80211_ATTR_RATE_STATS: Total number of rate-stats attributes + * @NL80211_ATTR_RATE_STATS_MAX: MAX rate-stats attribute number. + */ +enum nl80211_rate_stats { + __NL80211_RATE_STATS_INVALID, + NL80211_ATTR_RATE_STATS_RATE, + NL80211_ATTR_RATE_STATS_RX_PACKETS, + NL80211_ATTR_RATE_STATS_RX_BYTES, + + /* keep last */ + NUM_NL80211_ATTR_RATE_STATS, + NL80211_ATTR_RATE_STATS_MAX = NUM_NL80211_ATTR_RATE_STATS - 1 +}; +/** * enum nl80211_mpath_flags - nl80211 mesh path flags * * @NL80211_MPATH_FLAG_ACTIVE: the mesh path is active diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index afbe510..fa78d05 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -4826,6 +4826,170 @@ static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info) return err; } +static int +nl80211_send_sta_rate_stats(struct sk_buff *msg, u32 cmd, u32 portid, + u32 seq, int flags, + struct cfg80211_registered_device *rdev, + struct net_device *dev, const u8 *mac_addr, + struct sta_rate_stats *rate_stats_buf, + int rate_table_len) +{ + void *hdr; + struct sta_rate_stats *report; + struct nlattr *rstatsattr, *rentryattr; + int rid; + + hdr = nl80211hdr_put(msg, portid, seq, flags, cmd); + if (!hdr) + return -1; + + if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, dev->ifindex) || + nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr)) + goto nla_put_failure; + + if (rate_table_len && rate_stats_buf) { + rstatsattr = nla_nest_start(msg, NL80211_ATTR_RATE_STATS); + + if (!rstatsattr) + goto nla_put_failure; + + report = rate_stats_buf; + + for (rid = 0; rid < rate_table_len ; rid++) { + rentryattr = nla_nest_start(msg, rid + 1); + if (!rentryattr) + goto nla_put_failure; + + nla_put_u32(msg, NL80211_ATTR_RATE_STATS_RATE, + report->rate); + nla_put_u32(msg, NL80211_ATTR_RATE_STATS_RX_PACKETS, + report->packets); + nla_put_u64_64bit(msg, + NL80211_ATTR_RATE_STATS_RX_BYTES, + report->bytes, NL80211_ATTR_PAD); + + nla_nest_end(msg, rentryattr); + + if (rid + 1 < rate_table_len) + report++; + } + + nla_nest_end(msg, rstatsattr); + } + + genlmsg_end(msg, hdr); + return 0; + + nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + +static int nl80211_dump_sta_rate_stats(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct cfg80211_registered_device *rdev; + struct wireless_dev *wdev; + u8 mac_addr[ETH_ALEN]; + int sta_idx = cb->args[2]; + int err, num_rate_elems; + struct sta_rate_stats *rate_stats_buf = NULL; + + rtnl_lock(); + err = nl80211_prepare_wdev_dump(skb, cb, &rdev, &wdev); + if (err) + goto out_err; + + if (!wdev->netdev) { + err = -EINVAL; + goto out_err; + } + + if (!rdev->ops->dump_sta_rate_stats) { + err = -EOPNOTSUPP; + goto out_err; + } + + while (1) { + err = rdev_dump_sta_rate_stats(rdev, wdev->netdev, sta_idx, + mac_addr, &rate_stats_buf, + &num_rate_elems); + if (err == -ENOENT) + break; + if (err) + goto out_err; + + if (nl80211_send_sta_rate_stats(skb, NL80211_CMD_GET_RATE_STATS, + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, + rdev, wdev->netdev, mac_addr, + rate_stats_buf, + num_rate_elems) < 0) { + if (rate_stats_buf) + kfree(rate_stats_buf); + goto out; + } + + sta_idx++; + + if (rate_stats_buf) + kfree(rate_stats_buf); + } + + out: + cb->args[2] = sta_idx; + err = skb->len; + out_err: + rtnl_unlock(); + + return err; +} + +static int +nl80211_get_sta_rate_stats(struct sk_buff *skb, struct genl_info *info) +{ + struct cfg80211_registered_device *rdev = info->user_ptr[0]; + struct net_device *dev = info->user_ptr[1]; + struct sk_buff *msg; + u8 *mac_addr = NULL; + int err, num_rate_elems; + struct sta_rate_stats *rate_stats_buf = NULL; + + if (!info->attrs[NL80211_ATTR_MAC]) + return -EINVAL; + + mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]); + + if (!rdev->ops->get_sta_rate_stats) + return -EOPNOTSUPP; + + err = rdev_get_sta_rate_stats(rdev, dev, mac_addr, + &rate_stats_buf, &num_rate_elems); + if (err) + return err; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + if (nl80211_send_sta_rate_stats(msg, NL80211_CMD_GET_RATE_STATS, + info->snd_portid, info->snd_seq, 0, + rdev, dev, mac_addr, rate_stats_buf, + num_rate_elems) < 0) { + nlmsg_free(msg); + + if (rate_stats_buf) + kfree(rate_stats_buf); + + return -ENOBUFS; + } + + if (rate_stats_buf) + kfree(rate_stats_buf); + + return genlmsg_reply(msg, info); +} + int cfg80211_check_station_change(struct wiphy *wiphy, struct station_parameters *params, enum cfg80211_station_type statype) @@ -13744,6 +13908,14 @@ static const struct genl_ops nl80211_ops[] = { .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, + { + .cmd = NL80211_CMD_GET_RATE_STATS, + .doit = nl80211_get_sta_rate_stats, + .dumpit = nl80211_dump_sta_rate_stats, + .policy = nl80211_policy, + .internal_flags = NL80211_FLAG_NEED_NETDEV | + NL80211_FLAG_NEED_RTNL, + }, }; static struct genl_family nl80211_fam __ro_after_init = { diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h index 364f5d6..1564ad8 100644 --- a/net/wireless/rdev-ops.h +++ b/net/wireless/rdev-ops.h @@ -222,6 +222,36 @@ static inline int rdev_dump_station(struct cfg80211_registered_device *rdev, return ret; } +static inline int +rdev_get_sta_rate_stats(struct cfg80211_registered_device *rdev, + struct net_device *dev, const u8 *mac, + struct sta_rate_stats **rate_stats_buf, + int *len) +{ + int ret; + + trace_rdev_get_sta_rate_stats(&rdev->wiphy, dev, mac); + ret = rdev->ops->get_sta_rate_stats(&rdev->wiphy, dev, mac, + rate_stats_buf, len); + trace_rdev_return_int(&rdev->wiphy, ret); + return ret; +} + +static inline int +rdev_dump_sta_rate_stats(struct cfg80211_registered_device *rdev, + struct net_device *dev, int idx, u8 *mac, + struct sta_rate_stats **rate_stats_buf, + int *len) +{ + int ret; + + trace_rdev_dump_sta_rate_stats(&rdev->wiphy, dev, idx, mac); + ret = rdev->ops->dump_sta_rate_stats(&rdev->wiphy, dev, idx, mac, + rate_stats_buf, len); + trace_rdev_return_int(&rdev->wiphy, ret); + return ret; +} + static inline int rdev_add_mpath(struct cfg80211_registered_device *rdev, struct net_device *dev, u8 *dst, u8 *next_hop) { diff --git a/net/wireless/trace.h b/net/wireless/trace.h index 2b417a2..09dd69b 100644 --- a/net/wireless/trace.h +++ b/net/wireless/trace.h @@ -759,6 +759,11 @@ DEFINE_EVENT(wiphy_netdev_mac_evt, rdev_get_station, TP_ARGS(wiphy, netdev, mac) ); +DEFINE_EVENT(wiphy_netdev_mac_evt, rdev_get_sta_rate_stats, + TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, const u8 *mac), + TP_ARGS(wiphy, netdev, mac) +); + DEFINE_EVENT(wiphy_netdev_mac_evt, rdev_del_mpath, TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, const u8 *mac), TP_ARGS(wiphy, netdev, mac) @@ -790,6 +795,27 @@ TRACE_EVENT(rdev_dump_station, __entry->idx) ); +TRACE_EVENT(rdev_dump_sta_rate_stats, + TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, int idx, + u8 *mac), + TP_ARGS(wiphy, netdev, idx, mac), + TP_STRUCT__entry( + WIPHY_ENTRY + NETDEV_ENTRY + MAC_ENTRY(sta_mac) + __field(int, idx) + ), + TP_fast_assign( + WIPHY_ASSIGN; + NETDEV_ASSIGN; + MAC_ASSIGN(sta_mac, mac); + __entry->idx = idx; + ), + TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", station mac: " MAC_PR_FMT ", idx: %d", + WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(sta_mac), + __entry->idx) +); + TRACE_EVENT(rdev_return_int_station_info, TP_PROTO(struct wiphy *wiphy, int ret, struct station_info *sinfo), TP_ARGS(wiphy, ret, sinfo), -- 2.7.4