|
|
@@ -0,0 +1,888 @@
|
|
|
+From: Felix Fietkau <[email protected]>
|
|
|
+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 <[email protected]>
|
|
|
+---
|
|
|
+ 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 <[email protected]>
|
|
|
++ */
|
|
|
++#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);
|
|
|
++}
|