1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright (C) 2010 Felix Fietkau <nbd@openwrt.org>
5 #include <linux/netdevice.h>
6 #include <linux/types.h>
7 #include <linux/skbuff.h>
8 #include <linux/debugfs.h>
9 #include <linux/ieee80211.h>
10 #include <linux/export.h>
11 #include <net/mac80211.h>
12 #include "rc80211_minstrel_ht.h"
14 struct minstrel_debugfs_info
{
20 minstrel_stats_read(struct file
*file
, char __user
*buf
, size_t len
, loff_t
*ppos
)
22 struct minstrel_debugfs_info
*ms
;
24 ms
= file
->private_data
;
25 return simple_read_from_buffer(buf
, len
, ppos
, ms
->buf
, ms
->len
);
29 minstrel_stats_release(struct inode
*inode
, struct file
*file
)
31 kfree(file
->private_data
);
36 minstrel_ht_is_sample_rate(struct minstrel_ht_sta
*mi
, int idx
)
40 for (type
= 0; type
< ARRAY_SIZE(mi
->sample
); type
++)
41 for (i
= 0; i
< MINSTREL_SAMPLE_RATES
; i
++)
42 if (mi
->sample
[type
].cur_sample_rates
[i
] == idx
)
48 minstrel_ht_stats_dump(struct minstrel_ht_sta
*mi
, int i
, char *p
)
50 const struct mcs_group
*mg
;
51 unsigned int j
, tp_max
, tp_avg
, eprob
, tx_time
;
56 if (!mi
->supported
[i
])
59 mg
= &minstrel_mcs_groups
[i
];
62 if (gflags
& IEEE80211_TX_RC_40_MHZ_WIDTH
)
64 else if (gflags
& IEEE80211_TX_RC_80_MHZ_WIDTH
)
66 if (gflags
& IEEE80211_TX_RC_SHORT_GI
)
69 for (j
= 0; j
< MCS_GROUP_RATES
; j
++) {
70 struct minstrel_rate_stats
*mrs
= &mi
->groups
[i
].rates
[j
];
71 int idx
= MI_RATE(i
, j
);
72 unsigned int duration
;
74 if (!(mi
->supported
[i
] & BIT(j
)))
77 if (gflags
& IEEE80211_TX_RC_MCS
) {
78 p
+= sprintf(p
, "HT%c0 ", htmode
);
79 p
+= sprintf(p
, "%cGI ", gimode
);
80 p
+= sprintf(p
, "%d ", mg
->streams
);
81 } else if (gflags
& IEEE80211_TX_RC_VHT_MCS
) {
82 p
+= sprintf(p
, "VHT%c0 ", htmode
);
83 p
+= sprintf(p
, "%cGI ", gimode
);
84 p
+= sprintf(p
, "%d ", mg
->streams
);
85 } else if (i
== MINSTREL_OFDM_GROUP
) {
86 p
+= sprintf(p
, "OFDM ");
87 p
+= sprintf(p
, "1 ");
89 p
+= sprintf(p
, "CCK ");
90 p
+= sprintf(p
, "%cP ", j
< 4 ? 'L' : 'S');
91 p
+= sprintf(p
, "1 ");
94 *(p
++) = (idx
== mi
->max_tp_rate
[0]) ? 'A' : ' ';
95 *(p
++) = (idx
== mi
->max_tp_rate
[1]) ? 'B' : ' ';
96 *(p
++) = (idx
== mi
->max_tp_rate
[2]) ? 'C' : ' ';
97 *(p
++) = (idx
== mi
->max_tp_rate
[3]) ? 'D' : ' ';
98 *(p
++) = (idx
== mi
->max_prob_rate
) ? 'P' : ' ';
99 *(p
++) = minstrel_ht_is_sample_rate(mi
, idx
) ? 'S' : ' ';
101 if (gflags
& IEEE80211_TX_RC_MCS
) {
102 p
+= sprintf(p
, " MCS%-2u", (mg
->streams
- 1) * 8 + j
);
103 } else if (gflags
& IEEE80211_TX_RC_VHT_MCS
) {
104 p
+= sprintf(p
, " MCS%-1u/%1u", j
, mg
->streams
);
108 if (i
== MINSTREL_OFDM_GROUP
)
109 r
= minstrel_ofdm_bitrates
[j
% 8];
111 r
= minstrel_cck_bitrates
[j
% 4];
113 p
+= sprintf(p
, " %2u.%1uM", r
/ 10, r
% 10);
116 p
+= sprintf(p
, " %3u ", idx
);
118 /* tx_time[rate(i)] in usec */
119 duration
= mg
->duration
[j
];
120 duration
<<= mg
->shift
;
121 tx_time
= DIV_ROUND_CLOSEST(duration
, 1000);
122 p
+= sprintf(p
, "%6u ", tx_time
);
124 tp_max
= minstrel_ht_get_tp_avg(mi
, i
, j
, MINSTREL_FRAC(100, 100));
125 tp_avg
= minstrel_ht_get_tp_avg(mi
, i
, j
, mrs
->prob_avg
);
126 eprob
= MINSTREL_TRUNC(mrs
->prob_avg
* 1000);
128 p
+= sprintf(p
, "%4u.%1u %4u.%1u %3u.%1u"
131 tp_max
/ 10, tp_max
% 10,
132 tp_avg
/ 10, tp_avg
% 10,
133 eprob
/ 10, eprob
% 10,
137 (unsigned long long)mrs
->succ_hist
,
138 (unsigned long long)mrs
->att_hist
);
145 minstrel_ht_stats_open(struct inode
*inode
, struct file
*file
)
147 struct minstrel_ht_sta
*mi
= inode
->i_private
;
148 struct minstrel_debugfs_info
*ms
;
152 ms
= kmalloc(32768, GFP_KERNEL
);
156 file
->private_data
= ms
;
159 p
+= sprintf(p
, "\n");
161 " best ____________rate__________ ____statistics___ _____last____ ______sum-of________\n");
163 "mode guard # rate [name idx airtime max_tp] [avg(tp) avg(prob)] [retry|suc|att] [#success | #attempts]\n");
165 p
= minstrel_ht_stats_dump(mi
, MINSTREL_CCK_GROUP
, p
);
166 for (i
= 0; i
< MINSTREL_CCK_GROUP
; i
++)
167 p
= minstrel_ht_stats_dump(mi
, i
, p
);
168 for (i
++; i
< ARRAY_SIZE(mi
->groups
); i
++)
169 p
= minstrel_ht_stats_dump(mi
, i
, p
);
171 p
+= sprintf(p
, "\nTotal packet count:: ideal %d "
173 max(0, (int) mi
->total_packets
- (int) mi
->sample_packets
),
175 if (mi
->avg_ampdu_len
)
176 p
+= sprintf(p
, "Average # of aggregated frames per A-MPDU: %d.%d\n",
177 MINSTREL_TRUNC(mi
->avg_ampdu_len
),
178 MINSTREL_TRUNC(mi
->avg_ampdu_len
* 10) % 10);
179 ms
->len
= p
- ms
->buf
;
180 WARN_ON(ms
->len
+ sizeof(*ms
) > 32768);
182 return nonseekable_open(inode
, file
);
185 static const struct file_operations minstrel_ht_stat_fops
= {
186 .owner
= THIS_MODULE
,
187 .open
= minstrel_ht_stats_open
,
188 .read
= minstrel_stats_read
,
189 .release
= minstrel_stats_release
,
193 minstrel_ht_stats_csv_dump(struct minstrel_ht_sta
*mi
, int i
, char *p
)
195 const struct mcs_group
*mg
;
196 unsigned int j
, tp_max
, tp_avg
, eprob
, tx_time
;
201 if (!mi
->supported
[i
])
204 mg
= &minstrel_mcs_groups
[i
];
207 if (gflags
& IEEE80211_TX_RC_40_MHZ_WIDTH
)
209 else if (gflags
& IEEE80211_TX_RC_80_MHZ_WIDTH
)
211 if (gflags
& IEEE80211_TX_RC_SHORT_GI
)
214 for (j
= 0; j
< MCS_GROUP_RATES
; j
++) {
215 struct minstrel_rate_stats
*mrs
= &mi
->groups
[i
].rates
[j
];
216 int idx
= MI_RATE(i
, j
);
217 unsigned int duration
;
219 if (!(mi
->supported
[i
] & BIT(j
)))
222 if (gflags
& IEEE80211_TX_RC_MCS
) {
223 p
+= sprintf(p
, "HT%c0,", htmode
);
224 p
+= sprintf(p
, "%cGI,", gimode
);
225 p
+= sprintf(p
, "%d,", mg
->streams
);
226 } else if (gflags
& IEEE80211_TX_RC_VHT_MCS
) {
227 p
+= sprintf(p
, "VHT%c0,", htmode
);
228 p
+= sprintf(p
, "%cGI,", gimode
);
229 p
+= sprintf(p
, "%d,", mg
->streams
);
230 } else if (i
== MINSTREL_OFDM_GROUP
) {
231 p
+= sprintf(p
, "OFDM,,1,");
233 p
+= sprintf(p
, "CCK,");
234 p
+= sprintf(p
, "%cP,", j
< 4 ? 'L' : 'S');
235 p
+= sprintf(p
, "1,");
238 p
+= sprintf(p
, "%s" ,((idx
== mi
->max_tp_rate
[0]) ? "A" : ""));
239 p
+= sprintf(p
, "%s" ,((idx
== mi
->max_tp_rate
[1]) ? "B" : ""));
240 p
+= sprintf(p
, "%s" ,((idx
== mi
->max_tp_rate
[2]) ? "C" : ""));
241 p
+= sprintf(p
, "%s" ,((idx
== mi
->max_tp_rate
[3]) ? "D" : ""));
242 p
+= sprintf(p
, "%s" ,((idx
== mi
->max_prob_rate
) ? "P" : ""));
243 p
+= sprintf(p
, "%s", (minstrel_ht_is_sample_rate(mi
, idx
) ? "S" : ""));
245 if (gflags
& IEEE80211_TX_RC_MCS
) {
246 p
+= sprintf(p
, ",MCS%-2u,", (mg
->streams
- 1) * 8 + j
);
247 } else if (gflags
& IEEE80211_TX_RC_VHT_MCS
) {
248 p
+= sprintf(p
, ",MCS%-1u/%1u,", j
, mg
->streams
);
252 if (i
== MINSTREL_OFDM_GROUP
)
253 r
= minstrel_ofdm_bitrates
[j
% 8];
255 r
= minstrel_cck_bitrates
[j
% 4];
257 p
+= sprintf(p
, ",%2u.%1uM,", r
/ 10, r
% 10);
260 p
+= sprintf(p
, "%u,", idx
);
262 duration
= mg
->duration
[j
];
263 duration
<<= mg
->shift
;
264 tx_time
= DIV_ROUND_CLOSEST(duration
, 1000);
265 p
+= sprintf(p
, "%u,", tx_time
);
267 tp_max
= minstrel_ht_get_tp_avg(mi
, i
, j
, MINSTREL_FRAC(100, 100));
268 tp_avg
= minstrel_ht_get_tp_avg(mi
, i
, j
, mrs
->prob_avg
);
269 eprob
= MINSTREL_TRUNC(mrs
->prob_avg
* 1000);
271 p
+= sprintf(p
, "%u.%u,%u.%u,%u.%u,%u,%u,"
273 tp_max
/ 10, tp_max
% 10,
274 tp_avg
/ 10, tp_avg
% 10,
275 eprob
/ 10, eprob
% 10,
279 (unsigned long long)mrs
->succ_hist
,
280 (unsigned long long)mrs
->att_hist
);
281 p
+= sprintf(p
, "%d,%d,%d.%d\n",
282 max(0, (int) mi
->total_packets
-
283 (int) mi
->sample_packets
),
285 MINSTREL_TRUNC(mi
->avg_ampdu_len
),
286 MINSTREL_TRUNC(mi
->avg_ampdu_len
* 10) % 10);
293 minstrel_ht_stats_csv_open(struct inode
*inode
, struct file
*file
)
295 struct minstrel_ht_sta
*mi
= inode
->i_private
;
296 struct minstrel_debugfs_info
*ms
;
300 ms
= kmalloc(32768, GFP_KERNEL
);
304 file
->private_data
= ms
;
308 p
= minstrel_ht_stats_csv_dump(mi
, MINSTREL_CCK_GROUP
, p
);
309 for (i
= 0; i
< MINSTREL_CCK_GROUP
; i
++)
310 p
= minstrel_ht_stats_csv_dump(mi
, i
, p
);
311 for (i
++; i
< ARRAY_SIZE(mi
->groups
); i
++)
312 p
= minstrel_ht_stats_csv_dump(mi
, i
, p
);
314 ms
->len
= p
- ms
->buf
;
315 WARN_ON(ms
->len
+ sizeof(*ms
) > 32768);
317 return nonseekable_open(inode
, file
);
320 static const struct file_operations minstrel_ht_stat_csv_fops
= {
321 .owner
= THIS_MODULE
,
322 .open
= minstrel_ht_stats_csv_open
,
323 .read
= minstrel_stats_read
,
324 .release
= minstrel_stats_release
,
328 minstrel_ht_add_sta_debugfs(void *priv
, void *priv_sta
, struct dentry
*dir
)
330 debugfs_create_file("rc_stats", 0444, dir
, priv_sta
,
331 &minstrel_ht_stat_fops
);
332 debugfs_create_file("rc_stats_csv", 0444, dir
, priv_sta
,
333 &minstrel_ht_stat_csv_fops
);