mac80211: add api for monitoring/controlling rate control from user space
authorFelix Fietkau <nbd@nbd.name>
Mon, 1 Feb 2021 09:59:14 +0000 (10:59 +0100)
committerFelix Fietkau <nbd@nbd.name>
Mon, 18 Mar 2024 16:15:54 +0000 (17:15 +0100)
Signed-off-by: Felix Fietkau <nbd@nbd.name>
package/kernel/mac80211/Makefile
package/kernel/mac80211/patches/subsys/360-mac80211-minstrel-skip-memset-on-station-rate-contro.patch [new file with mode: 0644]
package/kernel/mac80211/patches/subsys/361-mac80211-minstrel_ht-add-debugfs-monitoring-controll.patch [new file with mode: 0644]

index 54a2bf4499273c377caa3cfea8bae385b1993404..efcea8ceacec1412952b7b4549f67771f8f6861f 100644 (file)
@@ -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
@@ -250,7 +250,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 (file)
index 0000000..813ecf4
--- /dev/null
@@ -0,0 +1,23 @@
+From: Felix Fietkau <nbd@nbd.name>
+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 <nbd@nbd.name>
+---
+
+--- a/net/mac80211/rc80211_minstrel_ht.c
++++ b/net/mac80211/rc80211_minstrel_ht.c
+@@ -1558,8 +1558,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 (file)
index 0000000..5e47081
--- /dev/null
@@ -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
+@@ -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;
+@@ -384,6 +385,7 @@ minstrel_ht_get_stats(struct minstrel_pr
+       idx = 0;
+ out:
++      *dest_idx = MI_RATE(group, idx);
+       return &mi->groups[group].rates[idx];
+ }
+@@ -1023,6 +1025,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;
+@@ -1030,7 +1034,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);
+@@ -1079,7 +1087,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 */
+@@ -1153,6 +1162,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 */
+@@ -1188,13 +1198,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,
+@@ -1216,7 +1228,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
+@@ -1279,7 +1291,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)
+ {
+@@ -1388,11 +1400,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;
+@@ -1419,7 +1435,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];
+@@ -1665,7 +1681,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
+@@ -1701,12 +1719,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);
+ }
+@@ -1817,12 +1841,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);
++}