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