[PATCH] mac80211: minstrel_ht: bound debugfs stats output construction
From: Pengpeng Hou
Date: Fri Apr 17 2026 - 04:10:34 EST
minstrel_ht_stats_open() and minstrel_ht_stats_csv_open() build their
entire debugfs outputs in a fixed 32 KiB heap buffer and append each row
with raw sprintf() calls.
The number of rows depends on the current-tree minstrel group/rate
layout, and the final WARN_ON() only checks the accumulated length after
the writes have already happened.
Allocate the debugfs buffer from the current maximum row count and use
bounded appends while constructing the stats and CSV outputs.
Fixes: 9208247d74bc ("mac80211: minstrel_ht: add basic support for VHT rates <= 3SS@80MHz")
Fixes: 2cae0b6a70d6 ("mac80211: add new Minstrel-HT statistic output via csv")
Signed-off-by: Pengpeng Hou <pengpeng@xxxxxxxxxxx>
---
net/mac80211/rc80211_minstrel_ht_debugfs.c | 263 +++++++++++++--------
1 file changed, 168 insertions(+), 95 deletions(-)
diff --git a/net/mac80211/rc80211_minstrel_ht_debugfs.c b/net/mac80211/rc80211_minstrel_ht_debugfs.c
index 85149c774505..91e414a799c1 100644
--- a/net/mac80211/rc80211_minstrel_ht_debugfs.c
+++ b/net/mac80211/rc80211_minstrel_ht_debugfs.c
@@ -8,14 +8,66 @@
#include <linux/debugfs.h>
#include <linux/ieee80211.h>
#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
#include <net/mac80211.h>
#include "rc80211_minstrel_ht.h"
struct minstrel_debugfs_info {
size_t len;
+ size_t size;
char buf[];
};
+#define MINSTREL_DEBUGFS_ROW_MAX 128
+
+static size_t minstrel_debugfs_remaining(struct minstrel_debugfs_info *ms,
+ char *p)
+{
+ return ms->size - (p - ms->buf);
+}
+
+static void minstrel_dbg_append(struct minstrel_debugfs_info *ms, char **pp,
+ const char *fmt, ...)
+{
+ size_t rem;
+ va_list args;
+ int len;
+
+ rem = minstrel_debugfs_remaining(ms, *pp);
+ if (!rem)
+ return;
+
+ va_start(args, fmt);
+ len = vscnprintf(*pp, rem, fmt, args);
+ va_end(args);
+
+ *pp += len;
+}
+
+static void minstrel_dbg_putc(struct minstrel_debugfs_info *ms, char **pp,
+ char ch)
+{
+ if (minstrel_debugfs_remaining(ms, *pp) <= 1)
+ return;
+
+ *(*pp)++ = ch;
+}
+
+static struct minstrel_debugfs_info *
+minstrel_debugfs_info_alloc(struct minstrel_ht_sta *mi)
+{
+ size_t rows = ARRAY_SIZE(mi->groups) * MCS_GROUP_RATES + 8;
+ size_t buf_size = rows * MINSTREL_DEBUGFS_ROW_MAX;
+ struct minstrel_debugfs_info *ms;
+
+ ms = kvzalloc(sizeof(*ms) + buf_size, GFP_KERNEL);
+ if (!ms)
+ return NULL;
+ ms->size = buf_size;
+ return ms;
+}
+
static ssize_t
minstrel_stats_read(struct file *file, char __user *buf, size_t len, loff_t *ppos)
{
@@ -28,7 +80,7 @@ minstrel_stats_read(struct file *file, char __user *buf, size_t len, loff_t *ppo
static int
minstrel_stats_release(struct inode *inode, struct file *file)
{
- kfree(file->private_data);
+ kvfree(file->private_data);
return 0;
}
@@ -45,7 +97,8 @@ minstrel_ht_is_sample_rate(struct minstrel_ht_sta *mi, int idx)
}
static char *
-minstrel_ht_stats_dump(struct minstrel_ht_sta *mi, int i, char *p)
+minstrel_ht_stats_dump(struct minstrel_ht_sta *mi, int i,
+ struct minstrel_debugfs_info *ms, char *p)
{
const struct mcs_group *mg;
unsigned int j, tp_max, tp_avg, eprob, tx_time;
@@ -75,33 +128,42 @@ minstrel_ht_stats_dump(struct minstrel_ht_sta *mi, int i, char *p)
continue;
if (gflags & IEEE80211_TX_RC_MCS) {
- p += sprintf(p, "HT%c0 ", htmode);
- p += sprintf(p, "%cGI ", gimode);
- p += sprintf(p, "%d ", mg->streams);
+ minstrel_dbg_append(ms, &p, "HT%c0 ", htmode);
+ minstrel_dbg_append(ms, &p, "%cGI ", gimode);
+ minstrel_dbg_append(ms, &p, "%d ", mg->streams);
} else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
- p += sprintf(p, "VHT%c0 ", htmode);
- p += sprintf(p, "%cGI ", gimode);
- p += sprintf(p, "%d ", mg->streams);
+ minstrel_dbg_append(ms, &p, "VHT%c0 ", htmode);
+ minstrel_dbg_append(ms, &p, "%cGI ", gimode);
+ minstrel_dbg_append(ms, &p, "%d ", mg->streams);
} else if (i == MINSTREL_OFDM_GROUP) {
- p += sprintf(p, "OFDM ");
- p += sprintf(p, "1 ");
+ minstrel_dbg_append(ms, &p, "OFDM ");
+ minstrel_dbg_append(ms, &p, "1 ");
} else {
- p += sprintf(p, "CCK ");
- p += sprintf(p, "%cP ", j < 4 ? 'L' : 'S');
- p += sprintf(p, "1 ");
+ minstrel_dbg_append(ms, &p, "CCK ");
+ minstrel_dbg_append(ms, &p, "%cP ",
+ j < 4 ? 'L' : 'S');
+ minstrel_dbg_append(ms, &p, "1 ");
}
- *(p++) = (idx == mi->max_tp_rate[0]) ? 'A' : ' ';
- *(p++) = (idx == mi->max_tp_rate[1]) ? 'B' : ' ';
- *(p++) = (idx == mi->max_tp_rate[2]) ? 'C' : ' ';
- *(p++) = (idx == mi->max_tp_rate[3]) ? 'D' : ' ';
- *(p++) = (idx == mi->max_prob_rate) ? 'P' : ' ';
- *(p++) = minstrel_ht_is_sample_rate(mi, idx) ? 'S' : ' ';
+ minstrel_dbg_putc(ms, &p,
+ (idx == mi->max_tp_rate[0]) ? 'A' : ' ');
+ minstrel_dbg_putc(ms, &p,
+ (idx == mi->max_tp_rate[1]) ? 'B' : ' ');
+ minstrel_dbg_putc(ms, &p,
+ (idx == mi->max_tp_rate[2]) ? 'C' : ' ');
+ minstrel_dbg_putc(ms, &p,
+ (idx == mi->max_tp_rate[3]) ? 'D' : ' ');
+ minstrel_dbg_putc(ms, &p,
+ (idx == mi->max_prob_rate) ? 'P' : ' ');
+ minstrel_dbg_putc(ms, &p,
+ minstrel_ht_is_sample_rate(mi, idx) ? 'S' : ' ');
if (gflags & IEEE80211_TX_RC_MCS) {
- p += sprintf(p, " MCS%-2u", (mg->streams - 1) * 8 + j);
+ minstrel_dbg_append(ms, &p, " MCS%-2u",
+ (mg->streams - 1) * 8 + j);
} else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
- p += sprintf(p, " MCS%-1u/%1u", j, mg->streams);
+ minstrel_dbg_append(ms, &p, " MCS%-1u/%1u",
+ j, mg->streams);
} else {
int r;
@@ -110,32 +172,34 @@ minstrel_ht_stats_dump(struct minstrel_ht_sta *mi, int i, char *p)
else
r = minstrel_cck_bitrates[j % 4];
- p += sprintf(p, " %2u.%1uM", r / 10, r % 10);
+ minstrel_dbg_append(ms, &p, " %2u.%1uM", r / 10,
+ r % 10);
}
- p += sprintf(p, " %3u ", idx);
+ minstrel_dbg_append(ms, &p, " %3u ", idx);
/* tx_time[rate(i)] in usec */
duration = mg->duration[j];
duration <<= mg->shift;
tx_time = DIV_ROUND_CLOSEST(duration, 1000);
- p += sprintf(p, "%6u ", tx_time);
+ minstrel_dbg_append(ms, &p, "%6u ", tx_time);
tp_max = minstrel_ht_get_tp_avg(mi, i, j, MINSTREL_FRAC(100, 100));
tp_avg = minstrel_ht_get_tp_avg(mi, i, j, mrs->prob_avg);
eprob = MINSTREL_TRUNC(mrs->prob_avg * 1000);
- p += sprintf(p, "%4u.%1u %4u.%1u %3u.%1u"
- " %3u %3u %-3u "
- "%9llu %-9llu\n",
- tp_max / 10, tp_max % 10,
- tp_avg / 10, tp_avg % 10,
- eprob / 10, eprob % 10,
- mrs->retry_count,
- mrs->last_success,
- mrs->last_attempts,
- (unsigned long long)mrs->succ_hist,
- (unsigned long long)mrs->att_hist);
+ minstrel_dbg_append(ms, &p,
+ "%4u.%1u %4u.%1u %3u.%1u ",
+ tp_max / 10, tp_max % 10,
+ tp_avg / 10, tp_avg % 10,
+ eprob / 10, eprob % 10);
+ minstrel_dbg_append(ms, &p, "%3u %3u %-3u ",
+ mrs->retry_count,
+ mrs->last_success,
+ mrs->last_attempts);
+ minstrel_dbg_append(ms, &p, "%9llu %-9llu\n",
+ (unsigned long long)mrs->succ_hist,
+ (unsigned long long)mrs->att_hist);
}
return p;
@@ -149,35 +213,35 @@ minstrel_ht_stats_open(struct inode *inode, struct file *file)
unsigned int i;
char *p;
- ms = kmalloc(32768, GFP_KERNEL);
+ ms = minstrel_debugfs_info_alloc(mi);
if (!ms)
return -ENOMEM;
file->private_data = ms;
p = ms->buf;
- p += sprintf(p, "\n");
- p += sprintf(p,
- " best ____________rate__________ ____statistics___ _____last____ ______sum-of________\n");
- p += sprintf(p,
- "mode guard # rate [name idx airtime max_tp] [avg(tp) avg(prob)] [retry|suc|att] [#success | #attempts]\n");
+ minstrel_dbg_append(ms, &p, "\n");
+ minstrel_dbg_append(ms, &p,
+ " best ____________rate__________ ____statistics___ _____last____ ______sum-of________\n");
+ minstrel_dbg_append(ms, &p,
+ "mode guard # rate [name idx airtime max_tp] [avg(tp) avg(prob)] [retry|suc|att] [#success | #attempts]\n");
- p = minstrel_ht_stats_dump(mi, MINSTREL_CCK_GROUP, p);
+ p = minstrel_ht_stats_dump(mi, MINSTREL_CCK_GROUP, ms, p);
for (i = 0; i < MINSTREL_CCK_GROUP; i++)
- p = minstrel_ht_stats_dump(mi, i, p);
+ p = minstrel_ht_stats_dump(mi, i, ms, p);
for (i++; i < ARRAY_SIZE(mi->groups); i++)
- p = minstrel_ht_stats_dump(mi, i, p);
+ p = minstrel_ht_stats_dump(mi, i, ms, p);
- p += sprintf(p, "\nTotal packet count:: ideal %d "
- "lookaround %d\n",
- max(0, (int) mi->total_packets - (int) mi->sample_packets),
- mi->sample_packets);
+ minstrel_dbg_append(ms, &p, "\nTotal packet count:: ideal %d ",
+ max(0, (int)mi->total_packets -
+ (int)mi->sample_packets));
+ minstrel_dbg_append(ms, &p, "lookaround %d\n", mi->sample_packets);
if (mi->avg_ampdu_len)
- p += sprintf(p, "Average # of aggregated frames per A-MPDU: %d.%d\n",
- MINSTREL_TRUNC(mi->avg_ampdu_len),
- MINSTREL_TRUNC(mi->avg_ampdu_len * 10) % 10);
+ minstrel_dbg_append(ms, &p,
+ "Average # of aggregated frames per A-MPDU: %d.%d\n",
+ MINSTREL_TRUNC(mi->avg_ampdu_len),
+ MINSTREL_TRUNC(mi->avg_ampdu_len * 10) % 10);
ms->len = p - ms->buf;
- WARN_ON(ms->len + sizeof(*ms) > 32768);
return nonseekable_open(inode, file);
}
@@ -190,7 +254,8 @@ static const struct file_operations minstrel_ht_stat_fops = {
};
static char *
-minstrel_ht_stats_csv_dump(struct minstrel_ht_sta *mi, int i, char *p)
+minstrel_ht_stats_csv_dump(struct minstrel_ht_sta *mi, int i,
+ struct minstrel_debugfs_info *ms, char *p)
{
const struct mcs_group *mg;
unsigned int j, tp_max, tp_avg, eprob, tx_time;
@@ -220,32 +285,40 @@ minstrel_ht_stats_csv_dump(struct minstrel_ht_sta *mi, int i, char *p)
continue;
if (gflags & IEEE80211_TX_RC_MCS) {
- p += sprintf(p, "HT%c0,", htmode);
- p += sprintf(p, "%cGI,", gimode);
- p += sprintf(p, "%d,", mg->streams);
+ minstrel_dbg_append(ms, &p, "HT%c0,", htmode);
+ minstrel_dbg_append(ms, &p, "%cGI,", gimode);
+ minstrel_dbg_append(ms, &p, "%d,", mg->streams);
} else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
- p += sprintf(p, "VHT%c0,", htmode);
- p += sprintf(p, "%cGI,", gimode);
- p += sprintf(p, "%d,", mg->streams);
+ minstrel_dbg_append(ms, &p, "VHT%c0,", htmode);
+ minstrel_dbg_append(ms, &p, "%cGI,", gimode);
+ minstrel_dbg_append(ms, &p, "%d,", mg->streams);
} else if (i == MINSTREL_OFDM_GROUP) {
- p += sprintf(p, "OFDM,,1,");
+ minstrel_dbg_append(ms, &p, "OFDM,,1,");
} else {
- p += sprintf(p, "CCK,");
- p += sprintf(p, "%cP,", j < 4 ? 'L' : 'S');
- p += sprintf(p, "1,");
+ minstrel_dbg_append(ms, &p, "CCK,");
+ minstrel_dbg_append(ms, &p, "%cP,", j < 4 ? 'L' : 'S');
+ minstrel_dbg_append(ms, &p, "1,");
}
- p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[0]) ? "A" : ""));
- p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[1]) ? "B" : ""));
- p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[2]) ? "C" : ""));
- p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[3]) ? "D" : ""));
- p += sprintf(p, "%s" ,((idx == mi->max_prob_rate) ? "P" : ""));
- p += sprintf(p, "%s", (minstrel_ht_is_sample_rate(mi, idx) ? "S" : ""));
+ minstrel_dbg_append(ms, &p, "%s",
+ (idx == mi->max_tp_rate[0]) ? "A" : "");
+ minstrel_dbg_append(ms, &p, "%s",
+ (idx == mi->max_tp_rate[1]) ? "B" : "");
+ minstrel_dbg_append(ms, &p, "%s",
+ (idx == mi->max_tp_rate[2]) ? "C" : "");
+ minstrel_dbg_append(ms, &p, "%s",
+ (idx == mi->max_tp_rate[3]) ? "D" : "");
+ minstrel_dbg_append(ms, &p, "%s",
+ (idx == mi->max_prob_rate) ? "P" : "");
+ minstrel_dbg_append(ms, &p, "%s",
+ minstrel_ht_is_sample_rate(mi, idx) ? "S" : "");
if (gflags & IEEE80211_TX_RC_MCS) {
- p += sprintf(p, ",MCS%-2u,", (mg->streams - 1) * 8 + j);
+ minstrel_dbg_append(ms, &p, ",MCS%-2u,",
+ (mg->streams - 1) * 8 + j);
} else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
- p += sprintf(p, ",MCS%-1u/%1u,", j, mg->streams);
+ minstrel_dbg_append(ms, &p, ",MCS%-1u/%1u,",
+ j, mg->streams);
} else {
int r;
@@ -254,36 +327,37 @@ minstrel_ht_stats_csv_dump(struct minstrel_ht_sta *mi, int i, char *p)
else
r = minstrel_cck_bitrates[j % 4];
- p += sprintf(p, ",%2u.%1uM,", r / 10, r % 10);
+ minstrel_dbg_append(ms, &p, ",%2u.%1uM,",
+ r / 10, r % 10);
}
- p += sprintf(p, "%u,", idx);
+ minstrel_dbg_append(ms, &p, "%u,", idx);
duration = mg->duration[j];
duration <<= mg->shift;
tx_time = DIV_ROUND_CLOSEST(duration, 1000);
- p += sprintf(p, "%u,", tx_time);
+ minstrel_dbg_append(ms, &p, "%u,", tx_time);
tp_max = minstrel_ht_get_tp_avg(mi, i, j, MINSTREL_FRAC(100, 100));
tp_avg = minstrel_ht_get_tp_avg(mi, i, j, mrs->prob_avg);
eprob = MINSTREL_TRUNC(mrs->prob_avg * 1000);
- p += sprintf(p, "%u.%u,%u.%u,%u.%u,%u,%u,"
- "%u,%llu,%llu,",
- tp_max / 10, tp_max % 10,
- tp_avg / 10, tp_avg % 10,
- eprob / 10, eprob % 10,
- mrs->retry_count,
- mrs->last_success,
- mrs->last_attempts,
- (unsigned long long)mrs->succ_hist,
- (unsigned long long)mrs->att_hist);
- p += sprintf(p, "%d,%d,%d.%d\n",
- max(0, (int) mi->total_packets -
- (int) mi->sample_packets),
- mi->sample_packets,
- MINSTREL_TRUNC(mi->avg_ampdu_len),
- MINSTREL_TRUNC(mi->avg_ampdu_len * 10) % 10);
+ minstrel_dbg_append(ms, &p, "%u.%u,%u.%u,%u.%u,%u,%u,%u,",
+ tp_max / 10, tp_max % 10,
+ tp_avg / 10, tp_avg % 10,
+ eprob / 10, eprob % 10,
+ mrs->retry_count,
+ mrs->last_success,
+ mrs->last_attempts);
+ minstrel_dbg_append(ms, &p, "%llu,%llu,",
+ (unsigned long long)mrs->succ_hist,
+ (unsigned long long)mrs->att_hist);
+ minstrel_dbg_append(ms, &p, "%d,%d,%d.%d\n",
+ max(0, (int)mi->total_packets -
+ (int)mi->sample_packets),
+ mi->sample_packets,
+ MINSTREL_TRUNC(mi->avg_ampdu_len),
+ MINSTREL_TRUNC(mi->avg_ampdu_len * 10) % 10);
}
return p;
@@ -297,7 +371,7 @@ minstrel_ht_stats_csv_open(struct inode *inode, struct file *file)
unsigned int i;
char *p;
- ms = kmalloc(32768, GFP_KERNEL);
+ ms = minstrel_debugfs_info_alloc(mi);
if (!ms)
return -ENOMEM;
@@ -305,14 +379,13 @@ minstrel_ht_stats_csv_open(struct inode *inode, struct file *file)
p = ms->buf;
- p = minstrel_ht_stats_csv_dump(mi, MINSTREL_CCK_GROUP, p);
+ p = minstrel_ht_stats_csv_dump(mi, MINSTREL_CCK_GROUP, ms, p);
for (i = 0; i < MINSTREL_CCK_GROUP; i++)
- p = minstrel_ht_stats_csv_dump(mi, i, p);
+ p = minstrel_ht_stats_csv_dump(mi, i, ms, p);
for (i++; i < ARRAY_SIZE(mi->groups); i++)
- p = minstrel_ht_stats_csv_dump(mi, i, p);
+ p = minstrel_ht_stats_csv_dump(mi, i, ms, p);
ms->len = p - ms->buf;
- WARN_ON(ms->len + sizeof(*ms) > 32768);
return nonseekable_open(inode, file);
}
--
2.50.1 (Apple Git-155)