From: Felix Fietkau Date: Mon, 1 Feb 2021 10:47:58 +0100 Subject: [PATCH] mac80211: minstrel_ht: add debugfs monitoring/controlling API This allows user space to monitor tx status and take over rate control functionality. Signed-off-by: Felix Fietkau --- create mode 100644 net/mac80211/rc80211_minstrel_ht_api.c --- a/local-symbols +++ b/local-symbols @@ -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 + */ +#include +#include +#include +#include +#include "rc80211_minstrel_ht.h" + +enum sta_cmd { + STA_CMD_PROBE, + STA_CMD_RATES, +}; + +static void +minstrel_ht_print_rate_durations(struct seq_file *s, int group) +{ + const struct mcs_group *g = &minstrel_mcs_groups[group]; + int n_rates; + int i; + + if (g->flags & IEEE80211_TX_RC_VHT_MCS) + n_rates = 10; + else + n_rates = 8; + + seq_printf(s, "%x", g->duration[0] << g->shift); + for (i = 1; i < n_rates; i++) + seq_printf(s, ",%x", g->duration[i] << g->shift); +} + +static int +minstrel_ht_read_api_info(struct seq_file *s, void *data) +{ + int i; + + seq_printf(s, "#group;index;offset;type;nss;bw;gi;airtime\n"); + seq_printf(s, "#sta;action;macaddr;overhead_mcs;overhead_legacy;supported\n"); + seq_printf(s, "#txs;macaddr;num_frames;num_acked;probe;rates;counts\n"); + seq_printf(s, "#stats;macaddr;rate;avg_prob;avg_tp;cur_success;cur_attempts;hist_success;hist_attempts\n"); + seq_printf(s, "#rates;macaddr;rates;counts\n"); + seq_printf(s, "#probe;macaddr;rate\n"); + for (i = 0; i < MINSTREL_GROUPS_NB; i++) { + const struct mcs_group *g = &minstrel_mcs_groups[i]; + const char *type; + + if (i == MINSTREL_CCK_GROUP) + type = "cck"; + else if (i == MINSTREL_OFDM_GROUP) + type = "ofdm"; + else if (g->flags & IEEE80211_TX_RC_VHT_MCS) + type = "vht"; + else + type = "ht"; + + seq_printf(s, "group;%x;%x;%s;%x;%x;%x;", + i, (u32) MI_RATE(i, 0), type, g->streams, g->bw, + !!(g->flags & IEEE80211_TX_RC_SHORT_GI)); + minstrel_ht_print_rate_durations(s, i); + seq_printf(s, "\n"); + } + + return 0; +} + +static struct dentry * +create_buf_file_cb(const char *filename, struct dentry *parent, umode_t mode, + struct rchan_buf *buf, int *is_global) +{ + struct dentry *f; + + f = debugfs_create_file("api_event", mode, parent, buf, + &relay_file_operations); + if (IS_ERR(f)) + return NULL; + + *is_global = 1; + + return f; +} + +static int +remove_buf_file_cb(struct dentry *f) +{ + debugfs_remove(f); + + return 0; +} + +static struct rchan_callbacks relay_ev_cb = { + .create_buf_file = create_buf_file_cb, + .remove_buf_file = remove_buf_file_cb, +}; + +static void +minstrel_ht_dump_sta(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, + const char *type) +{ + char info[64 + MINSTREL_GROUPS_NB * 4]; + int ofs = 0; + int i; + + ofs += scnprintf(info + ofs, sizeof(info) - ofs, "%llx;sta;%s;%pM;%x;%x;", + (unsigned long long)ktime_get_boottime_ns(), + type, mi->sta->addr, mi->overhead, mi->overhead_legacy); + + ofs += scnprintf(info + ofs, sizeof(info) - ofs, "%x", + mi->supported[0]); + for (i = 1; i < MINSTREL_GROUPS_NB; i++) + ofs += scnprintf(info + ofs, sizeof(info) - ofs, ",%x", + mi->supported[i]); + + ofs += scnprintf(info + ofs, sizeof(info) - ofs, "\n"); + relay_write(mp->relay_ev, info, ofs); + relay_flush(mp->relay_ev); +} + +static void +__minstrel_ht_dump_stations(struct minstrel_priv *mp, const char *type) +{ + struct minstrel_ht_sta *mi; + + list_for_each_entry(mi, &mp->stations, list) + minstrel_ht_dump_sta(mp, mi, type); +} + +static void +minstrel_ht_dump_stations(struct minstrel_priv *mp) +{ + spin_lock_bh(&mp->lock); + __minstrel_ht_dump_stations(mp, "dump"); + spin_unlock_bh(&mp->lock); +} + +static void +minstrel_ht_api_start(struct minstrel_priv *mp, char *params) +{ + char *cur; + u8 mask = 0; + + spin_lock_bh(&mp->lock); + + while ((cur = strsep(¶ms, ";")) != NULL) { + if (!strlen(cur)) + break; + + if (!strcmp(cur, "txs")) + mask |= MINSTREL_MONITOR_TXS; + else if (!strcmp(cur, "sta")) + mask |= MINSTREL_MONITOR_STA; + else if (!strcmp(cur, "stats")) + mask |= MINSTREL_MONITOR_STATS; + } + + if (!mask) + mask = MINSTREL_MONITOR_TXS; + + if (!mp->monitor) + __minstrel_ht_dump_stations(mp, "add"); + mp->monitor = mask | MINSTREL_MONITOR_STA; + + spin_unlock_bh(&mp->lock); +} + +static void +minstrel_ht_api_stop(struct minstrel_priv *mp) +{ + spin_lock_bh(&mp->lock); + mp->monitor = 0; + relay_reset(mp->relay_ev); + spin_unlock_bh(&mp->lock); +} + +static void +minstrel_ht_reset_sample_table(struct minstrel_ht_sta *mi) +{ + memset(mi->sample[MINSTREL_SAMPLE_TYPE_INC].sample_rates, 0, + sizeof(mi->sample[MINSTREL_SAMPLE_TYPE_INC].sample_rates)); +} + +static void +minstrel_ht_api_set_manual(struct minstrel_priv *mp, bool manual) +{ + struct minstrel_ht_sta *mi; + + mp->manual = manual; + + spin_lock_bh(&mp->lock); + list_for_each_entry(mi, &mp->stations, list) + minstrel_ht_reset_sample_table(mi); + spin_unlock_bh(&mp->lock); +} + +static struct minstrel_ht_sta * +minstrel_ht_api_get_sta(struct minstrel_priv *mp, const u8 *macaddr) +{ + struct minstrel_ht_sta *mi; + + list_for_each_entry(mi, &mp->stations, list) { + if (!memcmp(mi->sta->addr, macaddr, ETH_ALEN)) + return mi; + } + + return NULL; +} + +static int +minstrel_ht_get_args(char **dest, int dest_size, char *str, char *sep) +{ + int i, n; + + for (i = 0, n = 0; i < dest_size; i++) { + if (!str) { + dest[i] = NULL; + continue; + } + + dest[i] = strsep(&str, sep); + if (dest[i]) + n++; + } + + return n; +} + +static bool +minstrel_ht_valid_rate(struct minstrel_ht_sta *mi, u32 rate) +{ + int group, idx; + + group = MI_RATE_GROUP(rate); + if (group >= MINSTREL_GROUPS_NB) + return false; + + idx = MI_RATE_IDX(rate); + + return !!(mi->supported[group] & BIT(idx)); +} + +static int +minstrel_ht_rate_from_str(struct minstrel_ht_sta *mi, const char *str) +{ + unsigned int rate; + + if (kstrtouint(str, 16, &rate)) + return -EINVAL; + + if (!minstrel_ht_valid_rate(mi, rate)) + return -EINVAL; + + return rate; +} + +static int +minstrel_ht_set_probe_rate(struct minstrel_ht_sta *mi, const char *rate_str) +{ + u16 *sample_rates; + int rate, i; + + if (!rate_str) + return -EINVAL; + + rate = minstrel_ht_rate_from_str(mi, rate_str); + if (rate < 0) + return rate; + + sample_rates = mi->sample[MINSTREL_SAMPLE_TYPE_INC].sample_rates; + for (i = 0; i < MINSTREL_SAMPLE_RATES; i++) { + if (sample_rates[i]) + continue; + + sample_rates[i] = rate; + mi->sample_time = jiffies; + return 0; + } + + return -ENOSPC; +} + +static int +minstrel_ht_set_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, + char *rate_str, char *count_str) +{ + struct ieee80211_sta_rates *ratetbl; + unsigned int count; + char *countlist[4]; + char *ratelist[4]; + int rate; + int n_rates; + int n_count; + int err = -EINVAL; + int i; + + if (!rate_str || !count_str) + return -EINVAL; + + ratetbl = kzalloc(sizeof(*ratetbl), GFP_ATOMIC); + if (!ratetbl) + return -ENOMEM; + + n_rates = minstrel_ht_get_args(ratelist, ARRAY_SIZE(ratelist), + rate_str, ","); + n_count = minstrel_ht_get_args(countlist, ARRAY_SIZE(countlist), + count_str, ","); + for (i = 0; i < min(n_rates, n_count); i++) { + rate = minstrel_ht_rate_from_str(mi, ratelist[i]); + if (rate < 0) + goto error; + + if (kstrtouint(countlist[0], 16, &count)) + goto error; + + minstrel_ht_set_rate(mp, mi, ratetbl, i, rate); + ratetbl->rate[i].count = count; + ratetbl->rate[i].count_rts = count; + ratetbl->rate[i].count_cts = count; + } + + rate_control_set_rates(mp->hw, mi->sta, ratetbl); + + return 0; + +error: + kfree(ratetbl); + return err; +} + +static int +minstrel_ht_api_sta_cmd(struct minstrel_priv *mp, enum sta_cmd cmd, + char *arg_str) +{ + struct minstrel_ht_sta *mi; + uint8_t macaddr[ETH_ALEN]; + char *args[3]; + int n_args; + int ret = -EINVAL; + + spin_lock_bh(&mp->lock); + if (!mp->manual) + goto out; + + n_args = minstrel_ht_get_args(args, ARRAY_SIZE(args), arg_str, ";"); + if (!args[0]) + goto out; + + if (!mac_pton(args[0], macaddr)) + goto out; + + mi = minstrel_ht_api_get_sta(mp, macaddr); + if (!mi) { + ret = -ENOENT; + goto out; + } + + switch (cmd) { + case STA_CMD_PROBE: + ret = minstrel_ht_set_probe_rate(mi, args[1]); + break; + case STA_CMD_RATES: + ret = minstrel_ht_set_rates(mp, mi, args[1], args[2]); + break; + } + +out: + spin_unlock_bh(&mp->lock); + + return ret; +} + +static ssize_t +minstrel_ht_control_write(struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct minstrel_priv *mp = file->private_data; + char *pos, *cur; + char buf[64]; + size_t len = count; + int err; + + if (len > sizeof(buf) - 1) + return -EINVAL; + + if (copy_from_user(buf, userbuf, len)) + return -EFAULT; + + if (count > 0 && buf[len - 1] == '\n') + len--; + + buf[len] = 0; + if (!len) + return count; + + pos = buf; + cur = strsep(&pos, ";"); + + err = 0; + if (!strcmp(cur, "dump")) + minstrel_ht_dump_stations(mp); + else if (!strcmp(cur, "start")) + minstrel_ht_api_start(mp, pos); + else if (!strcmp(cur, "stop")) + minstrel_ht_api_stop(mp); + else if (!strcmp(cur, "manual")) + minstrel_ht_api_set_manual(mp, true); + else if (!strcmp(cur, "auto")) + minstrel_ht_api_set_manual(mp, false); + else if (!strcmp(cur, "rates")) + err = minstrel_ht_api_sta_cmd(mp, STA_CMD_RATES, pos); + else if (!strcmp(cur, "probe")) + err = minstrel_ht_api_sta_cmd(mp, STA_CMD_PROBE, pos); + else + err = -EINVAL; + + if (err) + return err; + + return count; +} + +static const struct file_operations fops_control = { + .open = simple_open, + .llseek = generic_file_llseek, + .write = minstrel_ht_control_write, +}; + +void minstrel_ht_sta_update(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) +{ + bool add = list_empty(&mi->list); + + spin_lock_bh(&mp->lock); + if (add) + list_add(&mi->list, &mp->stations); + if (mp->monitor) + minstrel_ht_dump_sta(mp, mi, add ? "add" : "update"); + spin_unlock_bh(&mp->lock); +} + +void minstrel_ht_sta_remove(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) +{ + char info[64]; + int ofs = 0; + + spin_lock_bh(&mp->lock); + list_del_init(&mi->list); + + if (!mp->monitor) + goto out; + + ofs = scnprintf(info, sizeof(info), "%llx;sta;remove;%pM;;;\n", + (unsigned long long)ktime_get_boottime_ns(), + mi->sta->addr); + relay_write(mp->relay_ev, info, ofs); + relay_flush(mp->relay_ev); + +out: + spin_unlock_bh(&mp->lock); +} + +void __minstrel_ht_report_tx_status(struct minstrel_priv *mp, + struct minstrel_ht_sta *mi, + struct ieee80211_tx_info *info, + u16 *rate_list, + int n_rates) +{ + char txs[64 + IEEE80211_TX_MAX_RATES * 8]; + int ofs = 0; + int i; + + if (!n_rates) + return; + + ofs += scnprintf(txs, sizeof(txs), "%llx;txs;%pM;%x;%x;%x;", + (unsigned long long)ktime_get_boottime_ns(), + mi->sta->addr, + info->status.ampdu_len, + info->status.ampdu_ack_len, + !!(info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE)); + + ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, "%x", + rate_list[0]); + for (i = 1; i < n_rates; i++) + ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, ",%x", + rate_list[i]); + + ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, ";%x", + info->status.rates[0].count); + for (i = 1; i < n_rates; i++) + ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, ",%x", + info->status.rates[i].count); + ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, "\n"); + relay_write(mp->relay_ev, txs, ofs); + relay_flush(mp->relay_ev); +} + +void __minstrel_ht_report_rate_update(struct minstrel_priv *mp, + struct minstrel_ht_sta *mi, u16 rate, + struct minstrel_rate_stats *mrs) +{ + char stat[100]; + int ofs; + int tp; + + tp = minstrel_ht_get_tp_avg(mi, MI_RATE_GROUP(rate), MI_RATE_IDX(rate), + mrs->prob_avg); + + ofs = scnprintf(stat, sizeof(stat), + "%llx;stats;%pM;%x;%x;%x;%x;%x;%x;%x\n", + (unsigned long long)ktime_get_boottime_ns(), + mi->sta->addr, rate, + MINSTREL_TRUNC(mrs->prob_avg * 1000), tp, + mrs->last_success, + mrs->last_attempts, + mrs->succ_hist, mrs->att_hist); + + relay_write(mp->relay_ev, stat, ofs); + relay_flush(mp->relay_ev); +} + +void minstrel_ht_add_debugfs_api(struct ieee80211_hw *hw, void *priv, + struct dentry *dir) +{ + struct minstrel_priv *mp = priv; + + spin_lock_init(&mp->lock); + INIT_LIST_HEAD(&mp->stations); + mp->relay_ev = relay_open("api_event", dir, 256, 512, &relay_ev_cb, + NULL); + debugfs_create_devm_seqfile(&hw->wiphy->dev, "api_info", + dir, minstrel_ht_read_api_info); + debugfs_create_file("api_control", 0200, dir, mp, &fops_control); +} + +void minstrel_ht_remove_debugfs_api(void *priv) +{ + struct minstrel_priv *mp = priv; + + if (mp->relay_ev) + relay_close(mp->relay_ev); +}