aboutsummaryrefslogtreecommitdiffstats
path: root/package/kernel/mac80211/patches/subsys/361-mac80211-minstrel_ht-add-debugfs-monitoring-controll.patch
diff options
context:
space:
mode:
authorFelix Fietkau <nbd@nbd.name>2021-06-18 09:20:54 +0200
committerFelix Fietkau <nbd@nbd.name>2021-06-18 09:52:35 +0200
commit53b6783907f3bd6f0f88f9d6feed20b21e2cd181 (patch)
tree7a305773b0e82b63abcabecf75fd9bbd7d422716 /package/kernel/mac80211/patches/subsys/361-mac80211-minstrel_ht-add-debugfs-monitoring-controll.patch
parenta603e82dd342680d584c4eb5f1b222e056379890 (diff)
downloadupstream-53b6783907f3bd6f0f88f9d6feed20b21e2cd181.tar.gz
upstream-53b6783907f3bd6f0f88f9d6feed20b21e2cd181.tar.bz2
upstream-53b6783907f3bd6f0f88f9d6feed20b21e2cd181.zip
mac80211: remove patches stripping down crypto support
Use of WPA3 and things like FILS is getting much more common, and platforms that can't affort the extra kilobytes for this code are fading away. Let's not hold back modern authentication methods any longer Signed-off-by: Felix Fietkau <nbd@nbd.name>
Diffstat (limited to 'package/kernel/mac80211/patches/subsys/361-mac80211-minstrel_ht-add-debugfs-monitoring-controll.patch')
-rw-r--r--package/kernel/mac80211/patches/subsys/361-mac80211-minstrel_ht-add-debugfs-monitoring-controll.patch888
1 files changed, 888 insertions, 0 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
new file mode 100644
index 0000000000..ef57234bea
--- /dev/null
+++ b/package/kernel/mac80211/patches/subsys/361-mac80211-minstrel_ht-add-debugfs-monitoring-controll.patch
@@ -0,0 +1,888 @@
+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(&params, ";")) != 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);
++}