Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0-only
2 : : /*
3 : : * Copyright (C) 2010 Felix Fietkau <nbd@openwrt.org>
4 : : */
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.h"
13 : : #include "rc80211_minstrel_ht.h"
14 : :
15 : : static ssize_t
16 : 0 : minstrel_stats_read(struct file *file, char __user *buf, size_t len, loff_t *ppos)
17 : : {
18 : 0 : struct minstrel_debugfs_info *ms;
19 : :
20 : 0 : ms = file->private_data;
21 : 0 : return simple_read_from_buffer(buf, len, ppos, ms->buf, ms->len);
22 : : }
23 : :
24 : : static int
25 : 0 : minstrel_stats_release(struct inode *inode, struct file *file)
26 : : {
27 : 0 : kfree(file->private_data);
28 : 0 : return 0;
29 : : }
30 : :
31 : : static char *
32 : 0 : minstrel_ht_stats_dump(struct minstrel_ht_sta *mi, int i, char *p)
33 : : {
34 : 0 : const struct mcs_group *mg;
35 : 0 : unsigned int j, tp_max, tp_avg, eprob, tx_time;
36 : 0 : char htmode = '2';
37 : 0 : char gimode = 'L';
38 : 0 : u32 gflags;
39 : :
40 [ # # ]: 0 : if (!mi->supported[i])
41 : : return p;
42 : :
43 : 0 : mg = &minstrel_mcs_groups[i];
44 : 0 : gflags = mg->flags;
45 : :
46 [ # # ]: 0 : if (gflags & IEEE80211_TX_RC_40_MHZ_WIDTH)
47 : : htmode = '4';
48 [ # # ]: 0 : else if (gflags & IEEE80211_TX_RC_80_MHZ_WIDTH)
49 : 0 : htmode = '8';
50 [ # # ]: 0 : if (gflags & IEEE80211_TX_RC_SHORT_GI)
51 : 0 : gimode = 'S';
52 : :
53 [ # # ]: 0 : for (j = 0; j < MCS_GROUP_RATES; j++) {
54 : 0 : struct minstrel_rate_stats *mrs = &mi->groups[i].rates[j];
55 : 0 : static const int bitrates[4] = { 10, 20, 55, 110 };
56 : 0 : int idx = i * MCS_GROUP_RATES + j;
57 : 0 : unsigned int duration;
58 : :
59 [ # # ]: 0 : if (!(mi->supported[i] & BIT(j)))
60 : 0 : continue;
61 : :
62 [ # # ]: 0 : if (gflags & IEEE80211_TX_RC_MCS) {
63 : 0 : p += sprintf(p, "HT%c0 ", htmode);
64 : 0 : p += sprintf(p, "%cGI ", gimode);
65 : 0 : p += sprintf(p, "%d ", mg->streams);
66 [ # # ]: 0 : } else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
67 : 0 : p += sprintf(p, "VHT%c0 ", htmode);
68 : 0 : p += sprintf(p, "%cGI ", gimode);
69 : 0 : p += sprintf(p, "%d ", mg->streams);
70 : : } else {
71 : 0 : p += sprintf(p, "CCK ");
72 [ # # ]: 0 : p += sprintf(p, "%cP ", j < 4 ? 'L' : 'S');
73 : 0 : p += sprintf(p, "1 ");
74 : : }
75 : :
76 [ # # ]: 0 : *(p++) = (idx == mi->max_tp_rate[0]) ? 'A' : ' ';
77 [ # # ]: 0 : *(p++) = (idx == mi->max_tp_rate[1]) ? 'B' : ' ';
78 [ # # ]: 0 : *(p++) = (idx == mi->max_tp_rate[2]) ? 'C' : ' ';
79 [ # # ]: 0 : *(p++) = (idx == mi->max_tp_rate[3]) ? 'D' : ' ';
80 [ # # ]: 0 : *(p++) = (idx == mi->max_prob_rate) ? 'P' : ' ';
81 : :
82 [ # # ]: 0 : if (gflags & IEEE80211_TX_RC_MCS) {
83 : 0 : p += sprintf(p, " MCS%-2u", (mg->streams - 1) * 8 + j);
84 [ # # ]: 0 : } else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
85 : 0 : p += sprintf(p, " MCS%-1u/%1u", j, mg->streams);
86 : : } else {
87 : 0 : int r = bitrates[j % 4];
88 : :
89 : 0 : p += sprintf(p, " %2u.%1uM", r / 10, r % 10);
90 : : }
91 : :
92 : 0 : p += sprintf(p, " %3u ", idx);
93 : :
94 : : /* tx_time[rate(i)] in usec */
95 : 0 : duration = mg->duration[j];
96 : 0 : duration <<= mg->shift;
97 : 0 : tx_time = DIV_ROUND_CLOSEST(duration, 1000);
98 : 0 : p += sprintf(p, "%6u ", tx_time);
99 : :
100 : 0 : tp_max = minstrel_ht_get_tp_avg(mi, i, j, MINSTREL_FRAC(100, 100));
101 : 0 : tp_avg = minstrel_ht_get_tp_avg(mi, i, j, mrs->prob_avg);
102 : 0 : eprob = MINSTREL_TRUNC(mrs->prob_avg * 1000);
103 : :
104 : 0 : p += sprintf(p, "%4u.%1u %4u.%1u %3u.%1u"
105 : : " %3u %3u %-3u "
106 : : "%9llu %-9llu\n",
107 : : tp_max / 10, tp_max % 10,
108 : : tp_avg / 10, tp_avg % 10,
109 : : eprob / 10, eprob % 10,
110 : 0 : mrs->retry_count,
111 : 0 : mrs->last_success,
112 : 0 : mrs->last_attempts,
113 : 0 : (unsigned long long)mrs->succ_hist,
114 : 0 : (unsigned long long)mrs->att_hist);
115 : : }
116 : :
117 : : return p;
118 : : }
119 : :
120 : : static int
121 : 0 : minstrel_ht_stats_open(struct inode *inode, struct file *file)
122 : : {
123 : 0 : struct minstrel_ht_sta_priv *msp = inode->i_private;
124 : 0 : struct minstrel_ht_sta *mi = &msp->ht;
125 : 0 : struct minstrel_debugfs_info *ms;
126 : 0 : unsigned int i;
127 : 0 : int ret;
128 : 0 : char *p;
129 : :
130 [ # # ]: 0 : if (!msp->is_ht) {
131 : 0 : inode->i_private = &msp->legacy;
132 : 0 : ret = minstrel_stats_open(inode, file);
133 : 0 : inode->i_private = msp;
134 : 0 : return ret;
135 : : }
136 : :
137 : 0 : ms = kmalloc(32768, GFP_KERNEL);
138 [ # # ]: 0 : if (!ms)
139 : : return -ENOMEM;
140 : :
141 : 0 : file->private_data = ms;
142 : 0 : p = ms->buf;
143 : :
144 : 0 : p += sprintf(p, "\n");
145 : 0 : p += sprintf(p,
146 : : " best ____________rate__________ ____statistics___ _____last____ ______sum-of________\n");
147 : 0 : p += sprintf(p,
148 : : "mode guard # rate [name idx airtime max_tp] [avg(tp) avg(prob)] [retry|suc|att] [#success | #attempts]\n");
149 : :
150 : 0 : p = minstrel_ht_stats_dump(mi, MINSTREL_CCK_GROUP, p);
151 [ # # ]: 0 : for (i = 0; i < MINSTREL_CCK_GROUP; i++)
152 : 0 : p = minstrel_ht_stats_dump(mi, i, p);
153 [ # # ]: 0 : for (i++; i < ARRAY_SIZE(mi->groups); i++)
154 : 0 : p = minstrel_ht_stats_dump(mi, i, p);
155 : :
156 : 0 : p += sprintf(p, "\nTotal packet count:: ideal %d "
157 : : "lookaround %d\n",
158 : 0 : max(0, (int) mi->total_packets - (int) mi->sample_packets),
159 : : mi->sample_packets);
160 [ # # ]: 0 : if (mi->avg_ampdu_len)
161 : 0 : p += sprintf(p, "Average # of aggregated frames per A-MPDU: %d.%d\n",
162 : : MINSTREL_TRUNC(mi->avg_ampdu_len),
163 : 0 : MINSTREL_TRUNC(mi->avg_ampdu_len * 10) % 10);
164 : 0 : ms->len = p - ms->buf;
165 [ # # ]: 0 : WARN_ON(ms->len + sizeof(*ms) > 32768);
166 : :
167 : 0 : return nonseekable_open(inode, file);
168 : : }
169 : :
170 : : static const struct file_operations minstrel_ht_stat_fops = {
171 : : .owner = THIS_MODULE,
172 : : .open = minstrel_ht_stats_open,
173 : : .read = minstrel_stats_read,
174 : : .release = minstrel_stats_release,
175 : : .llseek = no_llseek,
176 : : };
177 : :
178 : : static char *
179 : 0 : minstrel_ht_stats_csv_dump(struct minstrel_ht_sta *mi, int i, char *p)
180 : : {
181 : 0 : const struct mcs_group *mg;
182 : 0 : unsigned int j, tp_max, tp_avg, eprob, tx_time;
183 : 0 : char htmode = '2';
184 : 0 : char gimode = 'L';
185 : 0 : u32 gflags;
186 : :
187 [ # # ]: 0 : if (!mi->supported[i])
188 : : return p;
189 : :
190 : 0 : mg = &minstrel_mcs_groups[i];
191 : 0 : gflags = mg->flags;
192 : :
193 [ # # ]: 0 : if (gflags & IEEE80211_TX_RC_40_MHZ_WIDTH)
194 : : htmode = '4';
195 [ # # ]: 0 : else if (gflags & IEEE80211_TX_RC_80_MHZ_WIDTH)
196 : 0 : htmode = '8';
197 [ # # ]: 0 : if (gflags & IEEE80211_TX_RC_SHORT_GI)
198 : 0 : gimode = 'S';
199 : :
200 [ # # ]: 0 : for (j = 0; j < MCS_GROUP_RATES; j++) {
201 : 0 : struct minstrel_rate_stats *mrs = &mi->groups[i].rates[j];
202 : 0 : static const int bitrates[4] = { 10, 20, 55, 110 };
203 : 0 : int idx = i * MCS_GROUP_RATES + j;
204 : 0 : unsigned int duration;
205 : :
206 [ # # ]: 0 : if (!(mi->supported[i] & BIT(j)))
207 : 0 : continue;
208 : :
209 [ # # ]: 0 : if (gflags & IEEE80211_TX_RC_MCS) {
210 : 0 : p += sprintf(p, "HT%c0,", htmode);
211 : 0 : p += sprintf(p, "%cGI,", gimode);
212 : 0 : p += sprintf(p, "%d,", mg->streams);
213 [ # # ]: 0 : } else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
214 : 0 : p += sprintf(p, "VHT%c0,", htmode);
215 : 0 : p += sprintf(p, "%cGI,", gimode);
216 : 0 : p += sprintf(p, "%d,", mg->streams);
217 : : } else {
218 : 0 : p += sprintf(p, "CCK,");
219 [ # # ]: 0 : p += sprintf(p, "%cP,", j < 4 ? 'L' : 'S');
220 : 0 : p += sprintf(p, "1,");
221 : : }
222 : :
223 [ # # ]: 0 : p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[0]) ? "A" : ""));
224 [ # # ]: 0 : p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[1]) ? "B" : ""));
225 [ # # ]: 0 : p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[2]) ? "C" : ""));
226 [ # # ]: 0 : p += sprintf(p, "%s" ,((idx == mi->max_tp_rate[3]) ? "D" : ""));
227 [ # # ]: 0 : p += sprintf(p, "%s" ,((idx == mi->max_prob_rate) ? "P" : ""));
228 : :
229 [ # # ]: 0 : if (gflags & IEEE80211_TX_RC_MCS) {
230 : 0 : p += sprintf(p, ",MCS%-2u,", (mg->streams - 1) * 8 + j);
231 [ # # ]: 0 : } else if (gflags & IEEE80211_TX_RC_VHT_MCS) {
232 : 0 : p += sprintf(p, ",MCS%-1u/%1u,", j, mg->streams);
233 : : } else {
234 : 0 : int r = bitrates[j % 4];
235 : 0 : p += sprintf(p, ",%2u.%1uM,", r / 10, r % 10);
236 : : }
237 : :
238 : 0 : p += sprintf(p, "%u,", idx);
239 : :
240 : 0 : duration = mg->duration[j];
241 : 0 : duration <<= mg->shift;
242 : 0 : tx_time = DIV_ROUND_CLOSEST(duration, 1000);
243 : 0 : p += sprintf(p, "%u,", tx_time);
244 : :
245 : 0 : tp_max = minstrel_ht_get_tp_avg(mi, i, j, MINSTREL_FRAC(100, 100));
246 : 0 : tp_avg = minstrel_ht_get_tp_avg(mi, i, j, mrs->prob_avg);
247 : 0 : eprob = MINSTREL_TRUNC(mrs->prob_avg * 1000);
248 : :
249 : 0 : p += sprintf(p, "%u.%u,%u.%u,%u.%u,%u,%u,"
250 : : "%u,%llu,%llu,",
251 : : tp_max / 10, tp_max % 10,
252 : : tp_avg / 10, tp_avg % 10,
253 : : eprob / 10, eprob % 10,
254 : 0 : mrs->retry_count,
255 : 0 : mrs->last_success,
256 : 0 : mrs->last_attempts,
257 : 0 : (unsigned long long)mrs->succ_hist,
258 : 0 : (unsigned long long)mrs->att_hist);
259 : 0 : p += sprintf(p, "%d,%d,%d.%d\n",
260 : 0 : max(0, (int) mi->total_packets -
261 : : (int) mi->sample_packets),
262 : : mi->sample_packets,
263 : : MINSTREL_TRUNC(mi->avg_ampdu_len),
264 : 0 : MINSTREL_TRUNC(mi->avg_ampdu_len * 10) % 10);
265 : : }
266 : :
267 : : return p;
268 : : }
269 : :
270 : : static int
271 : 0 : minstrel_ht_stats_csv_open(struct inode *inode, struct file *file)
272 : : {
273 : 0 : struct minstrel_ht_sta_priv *msp = inode->i_private;
274 : 0 : struct minstrel_ht_sta *mi = &msp->ht;
275 : 0 : struct minstrel_debugfs_info *ms;
276 : 0 : unsigned int i;
277 : 0 : int ret;
278 : 0 : char *p;
279 : :
280 [ # # ]: 0 : if (!msp->is_ht) {
281 : 0 : inode->i_private = &msp->legacy;
282 : 0 : ret = minstrel_stats_csv_open(inode, file);
283 : 0 : inode->i_private = msp;
284 : 0 : return ret;
285 : : }
286 : :
287 : 0 : ms = kmalloc(32768, GFP_KERNEL);
288 : :
289 [ # # ]: 0 : if (!ms)
290 : : return -ENOMEM;
291 : :
292 : 0 : file->private_data = ms;
293 : :
294 : 0 : p = ms->buf;
295 : :
296 : 0 : p = minstrel_ht_stats_csv_dump(mi, MINSTREL_CCK_GROUP, p);
297 [ # # ]: 0 : for (i = 0; i < MINSTREL_CCK_GROUP; i++)
298 : 0 : p = minstrel_ht_stats_csv_dump(mi, i, p);
299 [ # # ]: 0 : for (i++; i < ARRAY_SIZE(mi->groups); i++)
300 : 0 : p = minstrel_ht_stats_csv_dump(mi, i, p);
301 : :
302 : 0 : ms->len = p - ms->buf;
303 [ # # ]: 0 : WARN_ON(ms->len + sizeof(*ms) > 32768);
304 : :
305 : 0 : return nonseekable_open(inode, file);
306 : : }
307 : :
308 : : static const struct file_operations minstrel_ht_stat_csv_fops = {
309 : : .owner = THIS_MODULE,
310 : : .open = minstrel_ht_stats_csv_open,
311 : : .read = minstrel_stats_read,
312 : : .release = minstrel_stats_release,
313 : : .llseek = no_llseek,
314 : : };
315 : :
316 : : void
317 : 0 : minstrel_ht_add_sta_debugfs(void *priv, void *priv_sta, struct dentry *dir)
318 : : {
319 : 0 : struct minstrel_ht_sta_priv *msp = priv_sta;
320 : :
321 : 0 : debugfs_create_file("rc_stats", 0444, dir, msp,
322 : : &minstrel_ht_stat_fops);
323 : 0 : debugfs_create_file("rc_stats_csv", 0444, dir, msp,
324 : : &minstrel_ht_stat_csv_fops);
325 : 0 : }
|