From: Felix Fietkau Date: Mon, 1 Feb 2021 09:59:14 +0000 (+0100) Subject: mac80211: add api for monitoring/controlling rate control from user space X-Git-Url: http://git.cdn.openwrt.org/?a=commitdiff_plain;h=d4ab52fb5ce18ae99d948182b725059be77246b5;p=openwrt%2Fstaging%2Fnbd.git mac80211: add api for monitoring/controlling rate control from user space Signed-off-by: Felix Fietkau --- diff --git a/package/kernel/mac80211/Makefile b/package/kernel/mac80211/Makefile index e3cf2d3800..41f7f96a82 100644 --- a/package/kernel/mac80211/Makefile +++ b/package/kernel/mac80211/Makefile @@ -126,7 +126,7 @@ define KernelPackage/mac80211 $(call KernelPackage/mac80211/Default) TITLE:=Linux 802.11 Wireless Networking Stack # +kmod-crypto-cmac is a runtime only dependency of net/mac80211/aes_cmac.c - DEPENDS+= +kmod-cfg80211 +kmod-crypto-cmac +kmod-crypto-ccm +kmod-crypto-gcm +hostapd-common + DEPENDS+= +kmod-cfg80211 +kmod-crypto-cmac +kmod-crypto-ccm +kmod-crypto-gcm +hostapd-common +@KERNEL_RELAY KCONFIG:=\ CONFIG_AVERAGE=y FILES:= $(PKG_BUILD_DIR)/net/mac80211/mac80211.ko @@ -383,7 +383,8 @@ endef ifdef CONFIG_PACKAGE_MAC80211_DEBUGFS config-y += \ CFG80211_DEBUGFS \ - MAC80211_DEBUGFS + MAC80211_DEBUGFS \ + MAC80211_RC_MINSTREL_DEBUGFS_API endif ifdef CONFIG_PACKAGE_MAC80211_TRACING diff --git a/package/kernel/mac80211/patches/subsys/360-mac80211-minstrel-skip-memset-on-station-rate-contro.patch b/package/kernel/mac80211/patches/subsys/360-mac80211-minstrel-skip-memset-on-station-rate-contro.patch new file mode 100644 index 0000000000..08920229d4 --- /dev/null +++ b/package/kernel/mac80211/patches/subsys/360-mac80211-minstrel-skip-memset-on-station-rate-contro.patch @@ -0,0 +1,23 @@ +From: Felix Fietkau +Date: Mon, 1 Feb 2021 10:44:04 +0100 +Subject: [PATCH] mac80211: minstrel: skip memset on station rate control + data + +With legacy minstrel support gone, there is no need to clear the data on +capability updates anymore. The supported flags are fully re-initialized, +and previous rate table statistics can be preserved. + +Signed-off-by: Felix Fietkau +--- + +--- a/net/mac80211/rc80211_minstrel_ht.c ++++ b/net/mac80211/rc80211_minstrel_ht.c +@@ -1555,8 +1555,6 @@ minstrel_ht_update_caps(void *priv, stru + else + use_vht = 0; + +- memset(mi, 0, sizeof(*mi)); +- + mi->sta = sta; + mi->band = sband->band; + mi->last_stats_update = jiffies; diff --git a/package/kernel/mac80211/patches/subsys/361-mac80211-minstrel_ht-add-debugfs-monitoring-controll.patch b/package/kernel/mac80211/patches/subsys/361-mac80211-minstrel_ht-add-debugfs-monitoring-controll.patch new file mode 100644 index 0000000000..525058e319 --- /dev/null +++ b/package/kernel/mac80211/patches/subsys/361-mac80211-minstrel_ht-add-debugfs-monitoring-controll.patch @@ -0,0 +1,888 @@ +From: Felix Fietkau +Date: Mon, 1 Feb 2021 10:47:58 +0100 +Subject: [PATCH] mac80211: minstrel_ht: add debugfs monitoring/controlling + API + +This allows user space to monitor tx status and take over rate control +functionality. + +Signed-off-by: Felix Fietkau +--- + create mode 100644 net/mac80211/rc80211_minstrel_ht_api.c + +--- a/local-symbols ++++ b/local-symbols +@@ -41,6 +41,7 @@ LIB80211_DEBUG= + MAC80211= + MAC80211_HAS_RC= + MAC80211_RC_MINSTREL= ++MAC80211_RC_MINSTREL_DEBUGFS_API= + MAC80211_RC_DEFAULT_MINSTREL= + MAC80211_RC_DEFAULT= + MAC80211_MESH= +--- a/net/mac80211/Kconfig ++++ b/net/mac80211/Kconfig +@@ -29,6 +29,15 @@ config MAC80211_RC_MINSTREL + help + This option enables the 'minstrel' TX rate control algorithm + ++config MAC80211_RC_MINSTREL_DEBUGFS_API ++ bool "Minstrel debugfs userspace control API" ++ depends on MAC80211_RC_MINSTREL ++ depends on MAC80211_DEBUGFS ++ select RELAY ++ help ++ This option creates debugfs files that allow user space to observe ++ and/or control minstrel rate selection behavior ++ + choice + prompt "Default rate control algorithm" + depends on MAC80211_HAS_RC +--- a/net/mac80211/Makefile ++++ b/net/mac80211/Makefile +@@ -61,6 +61,9 @@ rc80211_minstrel-y := \ + rc80211_minstrel-$(CPTCFG_MAC80211_DEBUGFS) += \ + rc80211_minstrel_ht_debugfs.o + ++rc80211_minstrel-$(CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API) += \ ++ rc80211_minstrel_ht_api.o ++ + mac80211-$(CPTCFG_MAC80211_RC_MINSTREL) += $(rc80211_minstrel-y) + + ccflags-y += -DDEBUG +--- a/net/mac80211/rc80211_minstrel_ht.c ++++ b/net/mac80211/rc80211_minstrel_ht.c +@@ -276,7 +276,8 @@ static const u8 minstrel_sample_seq[] = + }; + + static void +-minstrel_ht_update_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi); ++minstrel_ht_update_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, ++ bool force); + + /* + * Some VHT MCSes are invalid (when Ndbps / Nes is not an integer) +@@ -346,7 +347,7 @@ minstrel_vht_get_group_idx(struct ieee80 + + static struct minstrel_rate_stats * + minstrel_ht_get_stats(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, +- struct ieee80211_tx_rate *rate) ++ struct ieee80211_tx_rate *rate, u16 *dest_idx) + { + int group, idx; + +@@ -381,6 +382,7 @@ minstrel_ht_get_stats(struct minstrel_pr + + idx = 0; + out: ++ *dest_idx = MI_RATE(group, idx); + return &mi->groups[group].rates[idx]; + } + +@@ -1020,6 +1022,8 @@ minstrel_ht_update_stats(struct minstrel + tp_rate = tmp_legacy_tp_rate; + + for (i = MCS_GROUP_RATES - 1; i >= 0; i--) { ++ bool changed; ++ + if (!(mi->supported[group] & BIT(i))) + continue; + +@@ -1027,7 +1031,11 @@ minstrel_ht_update_stats(struct minstrel + + mrs = &mg->rates[i]; + mrs->retry_updated = false; ++ changed = mrs->attempts > 0; + minstrel_ht_calc_rate_stats(mp, mrs); ++ if (changed) ++ minstrel_ht_report_rate_update(mp, mi, index, ++ mrs); + + if (mrs->att_hist) + last_prob = max(last_prob, mrs->prob_avg); +@@ -1076,7 +1084,8 @@ minstrel_ht_update_stats(struct minstrel + + mi->max_prob_rate = tmp_max_prob_rate; + +- minstrel_ht_refill_sample_rates(mi); ++ if (!minstrel_ht_manual_mode(mp)) ++ minstrel_ht_refill_sample_rates(mi); + + #ifdef CPTCFG_MAC80211_DEBUGFS + /* use fixed index if set */ +@@ -1150,6 +1159,7 @@ minstrel_ht_tx_status(void *priv, struct + struct minstrel_priv *mp = priv; + u32 update_interval = mp->update_interval; + bool last, update = false; ++ u16 rate_list[IEEE80211_TX_MAX_RATES] = {}; + int i; + + /* Ignore packet that was sent with noAck flag */ +@@ -1185,13 +1195,15 @@ minstrel_ht_tx_status(void *priv, struct + last = (i == IEEE80211_TX_MAX_RATES - 1) || + !minstrel_ht_txstat_valid(mp, mi, &ar[i + 1]); + +- rate = minstrel_ht_get_stats(mp, mi, &ar[i]); ++ rate = minstrel_ht_get_stats(mp, mi, &ar[i], &rate_list[i]); + if (last) + rate->success += info->status.ampdu_ack_len; + + rate->attempts += ar[i].count * info->status.ampdu_len; + } + ++ minstrel_ht_report_tx_status(mp, mi, info, rate_list, i); ++ + if (mp->hw->max_rates > 1) { + /* + * check for sudden death of spatial multiplexing, +@@ -1213,7 +1225,7 @@ minstrel_ht_tx_status(void *priv, struct + } + + if (update) +- minstrel_ht_update_rates(mp, mi); ++ minstrel_ht_update_rates(mp, mi, false); + } + + static void +@@ -1276,7 +1288,7 @@ minstrel_calc_retransmit(struct minstrel + } + + +-static void ++void + minstrel_ht_set_rate(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, + struct ieee80211_sta_rates *ratetbl, int offset, int index) + { +@@ -1385,11 +1397,15 @@ minstrel_ht_get_max_amsdu_len(struct min + } + + static void +-minstrel_ht_update_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) ++minstrel_ht_update_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, ++ bool force) + { + struct ieee80211_sta_rates *rates; + int i = 0; + ++ if (minstrel_ht_manual_mode(mp) && !force) ++ return; ++ + rates = kzalloc(sizeof(*rates), GFP_ATOMIC); + if (!rates) + return; +@@ -1416,7 +1432,7 @@ minstrel_ht_get_sample_rate(struct minst + { + u8 seq; + +- if (mp->hw->max_rates > 1) { ++ if (mp->hw->max_rates > 1 && !minstrel_ht_manual_mode(mp)) { + seq = mi->sample_seq; + mi->sample_seq = (seq + 1) % ARRAY_SIZE(minstrel_sample_seq); + seq = minstrel_sample_seq[seq]; +@@ -1662,7 +1678,9 @@ minstrel_ht_update_caps(void *priv, stru + + /* create an initial rate table with the lowest supported rates */ + minstrel_ht_update_stats(mp, mi); +- minstrel_ht_update_rates(mp, mi); ++ minstrel_ht_update_rates(mp, mi, true); ++ ++ minstrel_ht_sta_update(mp, mi); + } + + static void +@@ -1698,12 +1716,18 @@ minstrel_ht_alloc_sta(void *priv, struct + max_rates = sband->n_bitrates; + } + +- return kzalloc(sizeof(*mi), gfp); ++ mi = kzalloc(sizeof(*mi), gfp); ++#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API ++ INIT_LIST_HEAD(&mi->list); ++#endif ++ ++ return mi; + } + + static void + minstrel_ht_free_sta(void *priv, struct ieee80211_sta *sta, void *priv_sta) + { ++ minstrel_ht_sta_remove(priv, priv_sta); + kfree(priv_sta); + } + +@@ -1814,12 +1838,14 @@ static void minstrel_ht_add_debugfs(stru + mp->fixed_rate_idx = (u32) -1; + debugfs_create_u32("fixed_rate_idx", S_IRUGO | S_IWUGO, debugfsdir, + &mp->fixed_rate_idx); ++ minstrel_ht_add_debugfs_api(hw, priv, debugfsdir); + } + #endif + + static void + minstrel_ht_free(void *priv) + { ++ minstrel_ht_remove_debugfs_api(priv); + kfree(priv); + } + +--- a/net/mac80211/rc80211_minstrel_ht.h ++++ b/net/mac80211/rc80211_minstrel_ht.h +@@ -72,6 +72,10 @@ + #define MINSTREL_SAMPLE_RATES 5 /* rates per sample type */ + #define MINSTREL_SAMPLE_INTERVAL (HZ / 50) + ++#define MINSTREL_MONITOR_STA BIT(0) ++#define MINSTREL_MONITOR_TXS BIT(1) ++#define MINSTREL_MONITOR_STATS BIT(2) ++ + struct minstrel_priv { + struct ieee80211_hw *hw; + bool has_mrr; +@@ -93,6 +97,13 @@ struct minstrel_priv { + */ + u32 fixed_rate_idx; + #endif ++#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API ++ struct rchan *relay_ev; ++ struct list_head stations; ++ spinlock_t lock; ++ u8 monitor; ++ bool manual; ++#endif + }; + + +@@ -153,6 +164,9 @@ struct minstrel_sample_category { + }; + + struct minstrel_ht_sta { ++#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API ++ struct list_head list; ++#endif + struct ieee80211_sta *sta; + + /* ampdu length (average, per sampling interval) */ +@@ -197,6 +211,80 @@ struct minstrel_ht_sta { + }; + + void minstrel_ht_add_sta_debugfs(void *priv, void *priv_sta, struct dentry *dir); ++ ++#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API ++void minstrel_ht_sta_update(struct minstrel_priv *mp, struct minstrel_ht_sta *mi); ++void minstrel_ht_sta_remove(struct minstrel_priv *mp, struct minstrel_ht_sta *mi); ++void __minstrel_ht_report_tx_status(struct minstrel_priv *mp, ++ struct minstrel_ht_sta *mi, ++ struct ieee80211_tx_info *info, ++ u16 *rate_list, int n_rates); ++void __minstrel_ht_report_rate_update(struct minstrel_priv *mp, ++ struct minstrel_ht_sta *mi, u16 rate, ++ struct minstrel_rate_stats *mrs); ++void minstrel_ht_add_debugfs_api(struct ieee80211_hw *hw, void *priv, ++ struct dentry *dir); ++void minstrel_ht_remove_debugfs_api(void *priv); ++#else ++static inline void ++minstrel_ht_sta_update(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) ++{ ++} ++static inline void ++minstrel_ht_sta_remove(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) ++{ ++} ++static inline void ++minstrel_ht_add_debugfs_api(struct ieee80211_hw *hw, void *priv, ++ struct dentry *dir) ++{ ++} ++static inline void ++minstrel_ht_remove_debugfs_api(void *priv) ++{ ++} ++#endif ++ ++static inline void ++minstrel_ht_report_tx_status(struct minstrel_priv *mp, ++ struct minstrel_ht_sta *mi, ++ struct ieee80211_tx_info *info, ++ u16 *rate_list, int n_rates) ++{ ++#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API ++ if (!(mp->monitor & MINSTREL_MONITOR_TXS)) ++ return; ++ ++ __minstrel_ht_report_tx_status(mp, mi, info, rate_list, n_rates); ++#endif ++} ++ ++static inline void ++minstrel_ht_report_rate_update(struct minstrel_priv *mp, ++ struct minstrel_ht_sta *mi, u16 rate, ++ struct minstrel_rate_stats *mrs) ++{ ++#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API ++ if (!(mp->monitor & MINSTREL_MONITOR_STATS)) ++ return; ++ ++ __minstrel_ht_report_rate_update(mp, mi, rate, mrs); ++#endif ++} ++ ++static inline bool ++minstrel_ht_manual_mode(struct minstrel_priv *mp) ++{ ++#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API ++ return mp->manual; ++#else ++ return false; ++#endif ++} ++ ++void minstrel_ht_set_rate(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, ++ struct ieee80211_sta_rates *ratetbl, int offset, ++ int index); + int minstrel_ht_get_tp_avg(struct minstrel_ht_sta *mi, int group, int rate, + int prob_avg); + +--- /dev/null ++++ b/net/mac80211/rc80211_minstrel_ht_api.c +@@ -0,0 +1,540 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2021 Felix Fietkau ++ */ ++#include ++#include ++#include ++#include ++#include "rc80211_minstrel_ht.h" ++ ++enum sta_cmd { ++ STA_CMD_PROBE, ++ STA_CMD_RATES, ++}; ++ ++static void ++minstrel_ht_print_rate_durations(struct seq_file *s, int group) ++{ ++ const struct mcs_group *g = &minstrel_mcs_groups[group]; ++ int n_rates; ++ int i; ++ ++ if (g->flags & IEEE80211_TX_RC_VHT_MCS) ++ n_rates = 10; ++ else ++ n_rates = 8; ++ ++ seq_printf(s, "%x", g->duration[0] << g->shift); ++ for (i = 1; i < n_rates; i++) ++ seq_printf(s, ",%x", g->duration[i] << g->shift); ++} ++ ++static int ++minstrel_ht_read_api_info(struct seq_file *s, void *data) ++{ ++ int i; ++ ++ seq_printf(s, "#group;index;offset;type;nss;bw;gi;airtime\n"); ++ seq_printf(s, "#sta;action;macaddr;overhead_mcs;overhead_legacy;supported\n"); ++ seq_printf(s, "#txs;macaddr;num_frames;num_acked;probe;rates;counts\n"); ++ seq_printf(s, "#stats;macaddr;rate;avg_prob;avg_tp;cur_success;cur_attempts;hist_success;hist_attempts\n"); ++ seq_printf(s, "#rates;macaddr;rates;counts\n"); ++ seq_printf(s, "#probe;macaddr;rate\n"); ++ for (i = 0; i < MINSTREL_GROUPS_NB; i++) { ++ const struct mcs_group *g = &minstrel_mcs_groups[i]; ++ const char *type; ++ ++ if (i == MINSTREL_CCK_GROUP) ++ type = "cck"; ++ else if (i == MINSTREL_OFDM_GROUP) ++ type = "ofdm"; ++ else if (g->flags & IEEE80211_TX_RC_VHT_MCS) ++ type = "vht"; ++ else ++ type = "ht"; ++ ++ seq_printf(s, "group;%x;%x;%s;%x;%x;%x;", ++ i, (u32) MI_RATE(i, 0), type, g->streams, g->bw, ++ !!(g->flags & IEEE80211_TX_RC_SHORT_GI)); ++ minstrel_ht_print_rate_durations(s, i); ++ seq_printf(s, "\n"); ++ } ++ ++ return 0; ++} ++ ++static struct dentry * ++create_buf_file_cb(const char *filename, struct dentry *parent, umode_t mode, ++ struct rchan_buf *buf, int *is_global) ++{ ++ struct dentry *f; ++ ++ f = debugfs_create_file("api_event", mode, parent, buf, ++ &relay_file_operations); ++ if (IS_ERR(f)) ++ return NULL; ++ ++ *is_global = 1; ++ ++ return f; ++} ++ ++static int ++remove_buf_file_cb(struct dentry *f) ++{ ++ debugfs_remove(f); ++ ++ return 0; ++} ++ ++static struct rchan_callbacks relay_ev_cb = { ++ .create_buf_file = create_buf_file_cb, ++ .remove_buf_file = remove_buf_file_cb, ++}; ++ ++static void ++minstrel_ht_dump_sta(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, ++ const char *type) ++{ ++ char info[64 + MINSTREL_GROUPS_NB * 4]; ++ int ofs = 0; ++ int i; ++ ++ ofs += scnprintf(info + ofs, sizeof(info) - ofs, "%llx;sta;%s;%pM;%x;%x;", ++ (unsigned long long)ktime_get_boottime_ns(), ++ type, mi->sta->addr, mi->overhead, mi->overhead_legacy); ++ ++ ofs += scnprintf(info + ofs, sizeof(info) - ofs, "%x", ++ mi->supported[0]); ++ for (i = 1; i < MINSTREL_GROUPS_NB; i++) ++ ofs += scnprintf(info + ofs, sizeof(info) - ofs, ",%x", ++ mi->supported[i]); ++ ++ ofs += scnprintf(info + ofs, sizeof(info) - ofs, "\n"); ++ relay_write(mp->relay_ev, info, ofs); ++ relay_flush(mp->relay_ev); ++} ++ ++static void ++__minstrel_ht_dump_stations(struct minstrel_priv *mp, const char *type) ++{ ++ struct minstrel_ht_sta *mi; ++ ++ list_for_each_entry(mi, &mp->stations, list) ++ minstrel_ht_dump_sta(mp, mi, type); ++} ++ ++static void ++minstrel_ht_dump_stations(struct minstrel_priv *mp) ++{ ++ spin_lock_bh(&mp->lock); ++ __minstrel_ht_dump_stations(mp, "dump"); ++ spin_unlock_bh(&mp->lock); ++} ++ ++static void ++minstrel_ht_api_start(struct minstrel_priv *mp, char *params) ++{ ++ char *cur; ++ u8 mask = 0; ++ ++ spin_lock_bh(&mp->lock); ++ ++ while ((cur = strsep(¶ms, ";")) != NULL) { ++ if (!strlen(cur)) ++ break; ++ ++ if (!strcmp(cur, "txs")) ++ mask |= MINSTREL_MONITOR_TXS; ++ else if (!strcmp(cur, "sta")) ++ mask |= MINSTREL_MONITOR_STA; ++ else if (!strcmp(cur, "stats")) ++ mask |= MINSTREL_MONITOR_STATS; ++ } ++ ++ if (!mask) ++ mask = MINSTREL_MONITOR_TXS; ++ ++ if (!mp->monitor) ++ __minstrel_ht_dump_stations(mp, "add"); ++ mp->monitor = mask | MINSTREL_MONITOR_STA; ++ ++ spin_unlock_bh(&mp->lock); ++} ++ ++static void ++minstrel_ht_api_stop(struct minstrel_priv *mp) ++{ ++ spin_lock_bh(&mp->lock); ++ mp->monitor = 0; ++ relay_reset(mp->relay_ev); ++ spin_unlock_bh(&mp->lock); ++} ++ ++static void ++minstrel_ht_reset_sample_table(struct minstrel_ht_sta *mi) ++{ ++ memset(mi->sample[MINSTREL_SAMPLE_TYPE_INC].sample_rates, 0, ++ sizeof(mi->sample[MINSTREL_SAMPLE_TYPE_INC].sample_rates)); ++} ++ ++static void ++minstrel_ht_api_set_manual(struct minstrel_priv *mp, bool manual) ++{ ++ struct minstrel_ht_sta *mi; ++ ++ mp->manual = manual; ++ ++ spin_lock_bh(&mp->lock); ++ list_for_each_entry(mi, &mp->stations, list) ++ minstrel_ht_reset_sample_table(mi); ++ spin_unlock_bh(&mp->lock); ++} ++ ++static struct minstrel_ht_sta * ++minstrel_ht_api_get_sta(struct minstrel_priv *mp, const u8 *macaddr) ++{ ++ struct minstrel_ht_sta *mi; ++ ++ list_for_each_entry(mi, &mp->stations, list) { ++ if (!memcmp(mi->sta->addr, macaddr, ETH_ALEN)) ++ return mi; ++ } ++ ++ return NULL; ++} ++ ++static int ++minstrel_ht_get_args(char **dest, int dest_size, char *str, char *sep) ++{ ++ int i, n; ++ ++ for (i = 0, n = 0; i < dest_size; i++) { ++ if (!str) { ++ dest[i] = NULL; ++ continue; ++ } ++ ++ dest[i] = strsep(&str, sep); ++ if (dest[i]) ++ n++; ++ } ++ ++ return n; ++} ++ ++static bool ++minstrel_ht_valid_rate(struct minstrel_ht_sta *mi, u32 rate) ++{ ++ int group, idx; ++ ++ group = MI_RATE_GROUP(rate); ++ if (group >= MINSTREL_GROUPS_NB) ++ return false; ++ ++ idx = MI_RATE_IDX(rate); ++ ++ return !!(mi->supported[group] & BIT(idx)); ++} ++ ++static int ++minstrel_ht_rate_from_str(struct minstrel_ht_sta *mi, const char *str) ++{ ++ unsigned int rate; ++ ++ if (kstrtouint(str, 16, &rate)) ++ return -EINVAL; ++ ++ if (!minstrel_ht_valid_rate(mi, rate)) ++ return -EINVAL; ++ ++ return rate; ++} ++ ++static int ++minstrel_ht_set_probe_rate(struct minstrel_ht_sta *mi, const char *rate_str) ++{ ++ u16 *sample_rates; ++ int rate, i; ++ ++ if (!rate_str) ++ return -EINVAL; ++ ++ rate = minstrel_ht_rate_from_str(mi, rate_str); ++ if (rate < 0) ++ return rate; ++ ++ sample_rates = mi->sample[MINSTREL_SAMPLE_TYPE_INC].sample_rates; ++ for (i = 0; i < MINSTREL_SAMPLE_RATES; i++) { ++ if (sample_rates[i]) ++ continue; ++ ++ sample_rates[i] = rate; ++ mi->sample_time = jiffies; ++ return 0; ++ } ++ ++ return -ENOSPC; ++} ++ ++static int ++minstrel_ht_set_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, ++ char *rate_str, char *count_str) ++{ ++ struct ieee80211_sta_rates *ratetbl; ++ unsigned int count; ++ char *countlist[4]; ++ char *ratelist[4]; ++ int rate; ++ int n_rates; ++ int n_count; ++ int err = -EINVAL; ++ int i; ++ ++ if (!rate_str || !count_str) ++ return -EINVAL; ++ ++ ratetbl = kzalloc(sizeof(*ratetbl), GFP_ATOMIC); ++ if (!ratetbl) ++ return -ENOMEM; ++ ++ n_rates = minstrel_ht_get_args(ratelist, ARRAY_SIZE(ratelist), ++ rate_str, ","); ++ n_count = minstrel_ht_get_args(countlist, ARRAY_SIZE(countlist), ++ count_str, ","); ++ for (i = 0; i < min(n_rates, n_count); i++) { ++ rate = minstrel_ht_rate_from_str(mi, ratelist[i]); ++ if (rate < 0) ++ goto error; ++ ++ if (kstrtouint(countlist[0], 16, &count)) ++ goto error; ++ ++ minstrel_ht_set_rate(mp, mi, ratetbl, i, rate); ++ ratetbl->rate[i].count = count; ++ ratetbl->rate[i].count_rts = count; ++ ratetbl->rate[i].count_cts = count; ++ } ++ ++ rate_control_set_rates(mp->hw, mi->sta, ratetbl); ++ ++ return 0; ++ ++error: ++ kfree(ratetbl); ++ return err; ++} ++ ++static int ++minstrel_ht_api_sta_cmd(struct minstrel_priv *mp, enum sta_cmd cmd, ++ char *arg_str) ++{ ++ struct minstrel_ht_sta *mi; ++ uint8_t macaddr[ETH_ALEN]; ++ char *args[3]; ++ int n_args; ++ int ret = -EINVAL; ++ ++ spin_lock_bh(&mp->lock); ++ if (!mp->manual) ++ goto out; ++ ++ n_args = minstrel_ht_get_args(args, ARRAY_SIZE(args), arg_str, ";"); ++ if (!args[0]) ++ goto out; ++ ++ if (!mac_pton(args[0], macaddr)) ++ goto out; ++ ++ mi = minstrel_ht_api_get_sta(mp, macaddr); ++ if (!mi) { ++ ret = -ENOENT; ++ goto out; ++ } ++ ++ switch (cmd) { ++ case STA_CMD_PROBE: ++ ret = minstrel_ht_set_probe_rate(mi, args[1]); ++ break; ++ case STA_CMD_RATES: ++ ret = minstrel_ht_set_rates(mp, mi, args[1], args[2]); ++ break; ++ } ++ ++out: ++ spin_unlock_bh(&mp->lock); ++ ++ return ret; ++} ++ ++static ssize_t ++minstrel_ht_control_write(struct file *file, const char __user *userbuf, ++ size_t count, loff_t *ppos) ++{ ++ struct minstrel_priv *mp = file->private_data; ++ char *pos, *cur; ++ char buf[64]; ++ size_t len = count; ++ int err; ++ ++ if (len > sizeof(buf) - 1) ++ return -EINVAL; ++ ++ if (copy_from_user(buf, userbuf, len)) ++ return -EFAULT; ++ ++ if (count > 0 && buf[len - 1] == '\n') ++ len--; ++ ++ buf[len] = 0; ++ if (!len) ++ return count; ++ ++ pos = buf; ++ cur = strsep(&pos, ";"); ++ ++ err = 0; ++ if (!strcmp(cur, "dump")) ++ minstrel_ht_dump_stations(mp); ++ else if (!strcmp(cur, "start")) ++ minstrel_ht_api_start(mp, pos); ++ else if (!strcmp(cur, "stop")) ++ minstrel_ht_api_stop(mp); ++ else if (!strcmp(cur, "manual")) ++ minstrel_ht_api_set_manual(mp, true); ++ else if (!strcmp(cur, "auto")) ++ minstrel_ht_api_set_manual(mp, false); ++ else if (!strcmp(cur, "rates")) ++ err = minstrel_ht_api_sta_cmd(mp, STA_CMD_RATES, pos); ++ else if (!strcmp(cur, "probe")) ++ err = minstrel_ht_api_sta_cmd(mp, STA_CMD_PROBE, pos); ++ else ++ err = -EINVAL; ++ ++ if (err) ++ return err; ++ ++ return count; ++} ++ ++static const struct file_operations fops_control = { ++ .open = simple_open, ++ .llseek = generic_file_llseek, ++ .write = minstrel_ht_control_write, ++}; ++ ++void minstrel_ht_sta_update(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) ++{ ++ bool add = list_empty(&mi->list); ++ ++ spin_lock_bh(&mp->lock); ++ if (add) ++ list_add(&mi->list, &mp->stations); ++ if (mp->monitor) ++ minstrel_ht_dump_sta(mp, mi, add ? "add" : "update"); ++ spin_unlock_bh(&mp->lock); ++} ++ ++void minstrel_ht_sta_remove(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) ++{ ++ char info[64]; ++ int ofs = 0; ++ ++ spin_lock_bh(&mp->lock); ++ list_del_init(&mi->list); ++ ++ if (!mp->monitor) ++ goto out; ++ ++ ofs = scnprintf(info, sizeof(info), "%llx;sta;remove;%pM;;;\n", ++ (unsigned long long)ktime_get_boottime_ns(), ++ mi->sta->addr); ++ relay_write(mp->relay_ev, info, ofs); ++ relay_flush(mp->relay_ev); ++ ++out: ++ spin_unlock_bh(&mp->lock); ++} ++ ++void __minstrel_ht_report_tx_status(struct minstrel_priv *mp, ++ struct minstrel_ht_sta *mi, ++ struct ieee80211_tx_info *info, ++ u16 *rate_list, ++ int n_rates) ++{ ++ char txs[64 + IEEE80211_TX_MAX_RATES * 8]; ++ int ofs = 0; ++ int i; ++ ++ if (!n_rates) ++ return; ++ ++ ofs += scnprintf(txs, sizeof(txs), "%llx;txs;%pM;%x;%x;%x;", ++ (unsigned long long)ktime_get_boottime_ns(), ++ mi->sta->addr, ++ info->status.ampdu_len, ++ info->status.ampdu_ack_len, ++ !!(info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE)); ++ ++ ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, "%x", ++ rate_list[0]); ++ for (i = 1; i < n_rates; i++) ++ ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, ",%x", ++ rate_list[i]); ++ ++ ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, ";%x", ++ info->status.rates[0].count); ++ for (i = 1; i < n_rates; i++) ++ ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, ",%x", ++ info->status.rates[i].count); ++ ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, "\n"); ++ relay_write(mp->relay_ev, txs, ofs); ++ relay_flush(mp->relay_ev); ++} ++ ++void __minstrel_ht_report_rate_update(struct minstrel_priv *mp, ++ struct minstrel_ht_sta *mi, u16 rate, ++ struct minstrel_rate_stats *mrs) ++{ ++ char stat[100]; ++ int ofs; ++ int tp; ++ ++ tp = minstrel_ht_get_tp_avg(mi, MI_RATE_GROUP(rate), MI_RATE_IDX(rate), ++ mrs->prob_avg); ++ ++ ofs = scnprintf(stat, sizeof(stat), ++ "%llx;stats;%pM;%x;%x;%x;%x;%x;%x;%x\n", ++ (unsigned long long)ktime_get_boottime_ns(), ++ mi->sta->addr, rate, ++ MINSTREL_TRUNC(mrs->prob_avg * 1000), tp, ++ mrs->last_success, ++ mrs->last_attempts, ++ mrs->succ_hist, mrs->att_hist); ++ ++ relay_write(mp->relay_ev, stat, ofs); ++ relay_flush(mp->relay_ev); ++} ++ ++void minstrel_ht_add_debugfs_api(struct ieee80211_hw *hw, void *priv, ++ struct dentry *dir) ++{ ++ struct minstrel_priv *mp = priv; ++ ++ spin_lock_init(&mp->lock); ++ INIT_LIST_HEAD(&mp->stations); ++ mp->relay_ev = relay_open("api_event", dir, 256, 512, &relay_ev_cb, ++ NULL); ++ debugfs_create_devm_seqfile(&hw->wiphy->dev, "api_info", ++ dir, minstrel_ht_read_api_info); ++ debugfs_create_file("api_control", 0200, dir, mp, &fops_control); ++} ++ ++void minstrel_ht_remove_debugfs_api(void *priv) ++{ ++ struct minstrel_priv *mp = priv; ++ ++ if (mp->relay_ev) ++ relay_close(mp->relay_ev); ++}