diff options
-rw-r--r-- | package/kernel/mac80211/patches/subsys/361-mac80211-minstrel_ht-add-debugfs-monitoring-controll.patch | 888 |
1 files changed, 0 insertions, 888 deletions
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 deleted file mode 100644 index ef57234bea..0000000000 --- a/package/kernel/mac80211/patches/subsys/361-mac80211-minstrel_ht-add-debugfs-monitoring-controll.patch +++ /dev/null @@ -1,888 +0,0 @@ -From: Felix Fietkau <nbd@nbd.name> -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 <nbd@nbd.name> ---- - create mode 100644 net/mac80211/rc80211_minstrel_ht_api.c - ---- a/local-symbols -+++ b/local-symbols -@@ -49,6 +49,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]; - } - -@@ -1024,6 +1026,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; - -@@ -1031,7 +1035,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); -@@ -1080,7 +1088,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 */ -@@ -1177,6 +1186,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; - - /* This packet was aggregated but doesn't carry status info */ -@@ -1208,13 +1218,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, -@@ -1236,7 +1248,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 -@@ -1299,7 +1311,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) - { -@@ -1408,11 +1420,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; -@@ -1439,7 +1455,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]; -@@ -1689,7 +1705,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 -@@ -1725,12 +1743,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); - } - -@@ -1841,12 +1865,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 <nbd@nbd.name> -+ */ -+#include <linux/kernel.h> -+#include <linux/debugfs.h> -+#include <linux/relay.h> -+#include <net/mac80211.h> -+#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); -+} |