]>
Commit | Line | Data |
---|---|---|
7bcfaf2f | 1 | |
e9f207f0 JB |
2 | /* |
3 | * mac80211 debugfs for wireless PHYs | |
4 | * | |
5 | * Copyright 2007 Johannes Berg <johannes@sipsolutions.net> | |
6 | * | |
7 | * GPLv2 | |
8 | * | |
9 | */ | |
10 | ||
11 | #include <linux/debugfs.h> | |
12 | #include <linux/rtnetlink.h> | |
13 | #include "ieee80211_i.h" | |
24487981 | 14 | #include "driver-ops.h" |
2c8dccc7 | 15 | #include "rate.h" |
e9f207f0 JB |
16 | #include "debugfs.h" |
17 | ||
18 | int mac80211_open_file_generic(struct inode *inode, struct file *file) | |
19 | { | |
20 | file->private_data = inode->i_private; | |
21 | return 0; | |
22 | } | |
23 | ||
07caf9d6 EP |
24 | #define DEBUGFS_FORMAT_BUFFER_SIZE 100 |
25 | ||
26 | int mac80211_format_buffer(char __user *userbuf, size_t count, | |
27 | loff_t *ppos, char *fmt, ...) | |
28 | { | |
29 | va_list args; | |
30 | char buf[DEBUGFS_FORMAT_BUFFER_SIZE]; | |
31 | int res; | |
32 | ||
33 | va_start(args, fmt); | |
34 | res = vscnprintf(buf, sizeof(buf), fmt, args); | |
35 | va_end(args); | |
36 | ||
37 | return simple_read_from_buffer(userbuf, count, ppos, buf, res); | |
38 | } | |
39 | ||
40 | #define DEBUGFS_READONLY_FILE(name, fmt, value...) \ | |
e9f207f0 JB |
41 | static ssize_t name## _read(struct file *file, char __user *userbuf, \ |
42 | size_t count, loff_t *ppos) \ | |
43 | { \ | |
44 | struct ieee80211_local *local = file->private_data; \ | |
e9f207f0 | 45 | \ |
07caf9d6 EP |
46 | return mac80211_format_buffer(userbuf, count, ppos, \ |
47 | fmt "\n", ##value); \ | |
e9f207f0 JB |
48 | } \ |
49 | \ | |
50 | static const struct file_operations name## _ops = { \ | |
51 | .read = name## _read, \ | |
52 | .open = mac80211_open_file_generic, \ | |
2b18ab36 | 53 | .llseek = generic_file_llseek, \ |
e9f207f0 JB |
54 | }; |
55 | ||
56 | #define DEBUGFS_ADD(name) \ | |
7bcfaf2f | 57 | debugfs_create_file(#name, 0400, phyd, local, &name## _ops); |
e9f207f0 | 58 | |
827b1fb4 | 59 | #define DEBUGFS_ADD_MODE(name, mode) \ |
7bcfaf2f | 60 | debugfs_create_file(#name, mode, phyd, local, &name## _ops); |
e9f207f0 JB |
61 | |
62 | ||
07caf9d6 | 63 | DEBUGFS_READONLY_FILE(frequency, "%d", |
8318d78a | 64 | local->hw.conf.channel->center_freq); |
07caf9d6 | 65 | DEBUGFS_READONLY_FILE(total_ps_buffered, "%d", |
e9f207f0 | 66 | local->total_ps_buffered); |
07caf9d6 | 67 | DEBUGFS_READONLY_FILE(wep_iv, "%#08x", |
e9f207f0 | 68 | local->wep_iv & 0xffffff); |
07caf9d6 | 69 | DEBUGFS_READONLY_FILE(rate_ctrl_alg, "%s", |
af65cd96 | 70 | local->rate_ctrl ? local->rate_ctrl->ops->name : "hw/driver"); |
3b5d665b AF |
71 | |
72 | static ssize_t tsf_read(struct file *file, char __user *user_buf, | |
73 | size_t count, loff_t *ppos) | |
74 | { | |
75 | struct ieee80211_local *local = file->private_data; | |
24487981 | 76 | u64 tsf; |
3b5d665b | 77 | |
24487981 | 78 | tsf = drv_get_tsf(local); |
3b5d665b | 79 | |
07caf9d6 EP |
80 | return mac80211_format_buffer(user_buf, count, ppos, "0x%016llx\n", |
81 | (unsigned long long) tsf); | |
3b5d665b AF |
82 | } |
83 | ||
84 | static ssize_t tsf_write(struct file *file, | |
85 | const char __user *user_buf, | |
86 | size_t count, loff_t *ppos) | |
87 | { | |
88 | struct ieee80211_local *local = file->private_data; | |
89 | unsigned long long tsf; | |
90 | char buf[100]; | |
91 | size_t len; | |
92 | ||
93 | len = min(count, sizeof(buf) - 1); | |
94 | if (copy_from_user(buf, user_buf, len)) | |
95 | return -EFAULT; | |
96 | buf[len] = '\0'; | |
97 | ||
98 | if (strncmp(buf, "reset", 5) == 0) { | |
99 | if (local->ops->reset_tsf) { | |
24487981 | 100 | drv_reset_tsf(local); |
0fb9a9ec | 101 | wiphy_info(local->hw.wiphy, "debugfs reset TSF\n"); |
3b5d665b AF |
102 | } |
103 | } else { | |
104 | tsf = simple_strtoul(buf, NULL, 0); | |
105 | if (local->ops->set_tsf) { | |
24487981 | 106 | drv_set_tsf(local, tsf); |
0fb9a9ec JP |
107 | wiphy_info(local->hw.wiphy, |
108 | "debugfs set TSF to %#018llx\n", tsf); | |
109 | ||
3b5d665b AF |
110 | } |
111 | } | |
112 | ||
113 | return count; | |
114 | } | |
115 | ||
116 | static const struct file_operations tsf_ops = { | |
117 | .read = tsf_read, | |
118 | .write = tsf_write, | |
6038f373 AB |
119 | .open = mac80211_open_file_generic, |
120 | .llseek = default_llseek, | |
3b5d665b | 121 | }; |
e9f207f0 | 122 | |
827b1fb4 JB |
123 | static ssize_t reset_write(struct file *file, const char __user *user_buf, |
124 | size_t count, loff_t *ppos) | |
125 | { | |
126 | struct ieee80211_local *local = file->private_data; | |
127 | ||
128 | rtnl_lock(); | |
129 | __ieee80211_suspend(&local->hw); | |
130 | __ieee80211_resume(&local->hw); | |
131 | rtnl_unlock(); | |
132 | ||
133 | return count; | |
134 | } | |
135 | ||
136 | static const struct file_operations reset_ops = { | |
137 | .write = reset_write, | |
138 | .open = mac80211_open_file_generic, | |
6038f373 | 139 | .llseek = noop_llseek, |
827b1fb4 JB |
140 | }; |
141 | ||
d3707d99 JB |
142 | static ssize_t noack_read(struct file *file, char __user *user_buf, |
143 | size_t count, loff_t *ppos) | |
144 | { | |
145 | struct ieee80211_local *local = file->private_data; | |
d3707d99 | 146 | |
07caf9d6 EP |
147 | return mac80211_format_buffer(user_buf, count, ppos, "%d\n", |
148 | local->wifi_wme_noack_test); | |
d3707d99 JB |
149 | } |
150 | ||
151 | static ssize_t noack_write(struct file *file, | |
152 | const char __user *user_buf, | |
153 | size_t count, loff_t *ppos) | |
154 | { | |
155 | struct ieee80211_local *local = file->private_data; | |
156 | char buf[10]; | |
157 | size_t len; | |
158 | ||
159 | len = min(count, sizeof(buf) - 1); | |
160 | if (copy_from_user(buf, user_buf, len)) | |
161 | return -EFAULT; | |
162 | buf[len] = '\0'; | |
163 | ||
164 | local->wifi_wme_noack_test = !!simple_strtoul(buf, NULL, 0); | |
165 | ||
166 | return count; | |
167 | } | |
168 | ||
169 | static const struct file_operations noack_ops = { | |
170 | .read = noack_read, | |
171 | .write = noack_write, | |
6038f373 AB |
172 | .open = mac80211_open_file_generic, |
173 | .llseek = default_llseek, | |
d3707d99 JB |
174 | }; |
175 | ||
50ae0cf1 KV |
176 | static ssize_t uapsd_queues_read(struct file *file, char __user *user_buf, |
177 | size_t count, loff_t *ppos) | |
178 | { | |
179 | struct ieee80211_local *local = file->private_data; | |
07caf9d6 EP |
180 | return mac80211_format_buffer(user_buf, count, ppos, "0x%x\n", |
181 | local->uapsd_queues); | |
50ae0cf1 KV |
182 | } |
183 | ||
184 | static ssize_t uapsd_queues_write(struct file *file, | |
185 | const char __user *user_buf, | |
186 | size_t count, loff_t *ppos) | |
187 | { | |
188 | struct ieee80211_local *local = file->private_data; | |
189 | unsigned long val; | |
190 | char buf[10]; | |
191 | size_t len; | |
192 | int ret; | |
193 | ||
194 | len = min(count, sizeof(buf) - 1); | |
195 | if (copy_from_user(buf, user_buf, len)) | |
196 | return -EFAULT; | |
197 | buf[len] = '\0'; | |
198 | ||
199 | ret = strict_strtoul(buf, 0, &val); | |
200 | ||
201 | if (ret) | |
202 | return -EINVAL; | |
203 | ||
204 | if (val & ~IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK) | |
205 | return -ERANGE; | |
206 | ||
207 | local->uapsd_queues = val; | |
208 | ||
209 | return count; | |
210 | } | |
211 | ||
212 | static const struct file_operations uapsd_queues_ops = { | |
213 | .read = uapsd_queues_read, | |
214 | .write = uapsd_queues_write, | |
6038f373 AB |
215 | .open = mac80211_open_file_generic, |
216 | .llseek = default_llseek, | |
50ae0cf1 KV |
217 | }; |
218 | ||
219 | static ssize_t uapsd_max_sp_len_read(struct file *file, char __user *user_buf, | |
220 | size_t count, loff_t *ppos) | |
221 | { | |
222 | struct ieee80211_local *local = file->private_data; | |
50ae0cf1 | 223 | |
07caf9d6 EP |
224 | return mac80211_format_buffer(user_buf, count, ppos, "0x%x\n", |
225 | local->uapsd_max_sp_len); | |
50ae0cf1 KV |
226 | } |
227 | ||
228 | static ssize_t uapsd_max_sp_len_write(struct file *file, | |
229 | const char __user *user_buf, | |
230 | size_t count, loff_t *ppos) | |
231 | { | |
232 | struct ieee80211_local *local = file->private_data; | |
233 | unsigned long val; | |
234 | char buf[10]; | |
235 | size_t len; | |
236 | int ret; | |
237 | ||
238 | len = min(count, sizeof(buf) - 1); | |
239 | if (copy_from_user(buf, user_buf, len)) | |
240 | return -EFAULT; | |
241 | buf[len] = '\0'; | |
242 | ||
243 | ret = strict_strtoul(buf, 0, &val); | |
244 | ||
245 | if (ret) | |
246 | return -EINVAL; | |
247 | ||
248 | if (val & ~IEEE80211_WMM_IE_STA_QOSINFO_SP_MASK) | |
249 | return -ERANGE; | |
250 | ||
251 | local->uapsd_max_sp_len = val; | |
252 | ||
253 | return count; | |
254 | } | |
255 | ||
256 | static const struct file_operations uapsd_max_sp_len_ops = { | |
257 | .read = uapsd_max_sp_len_read, | |
258 | .write = uapsd_max_sp_len_write, | |
6038f373 AB |
259 | .open = mac80211_open_file_generic, |
260 | .llseek = default_llseek, | |
50ae0cf1 KV |
261 | }; |
262 | ||
199d69f2 BP |
263 | static ssize_t channel_type_read(struct file *file, char __user *user_buf, |
264 | size_t count, loff_t *ppos) | |
265 | { | |
266 | struct ieee80211_local *local = file->private_data; | |
267 | const char *buf; | |
268 | ||
269 | switch (local->hw.conf.channel_type) { | |
270 | case NL80211_CHAN_NO_HT: | |
271 | buf = "no ht\n"; | |
272 | break; | |
273 | case NL80211_CHAN_HT20: | |
274 | buf = "ht20\n"; | |
275 | break; | |
276 | case NL80211_CHAN_HT40MINUS: | |
277 | buf = "ht40-\n"; | |
278 | break; | |
279 | case NL80211_CHAN_HT40PLUS: | |
280 | buf = "ht40+\n"; | |
281 | break; | |
282 | default: | |
283 | buf = "???"; | |
284 | break; | |
285 | } | |
286 | ||
287 | return simple_read_from_buffer(user_buf, count, ppos, buf, strlen(buf)); | |
288 | } | |
289 | ||
290 | static const struct file_operations channel_type_ops = { | |
291 | .read = channel_type_read, | |
6038f373 AB |
292 | .open = mac80211_open_file_generic, |
293 | .llseek = default_llseek, | |
199d69f2 BP |
294 | }; |
295 | ||
db2e6bd4 JB |
296 | static ssize_t queues_read(struct file *file, char __user *user_buf, |
297 | size_t count, loff_t *ppos) | |
298 | { | |
299 | struct ieee80211_local *local = file->private_data; | |
300 | unsigned long flags; | |
301 | char buf[IEEE80211_MAX_QUEUES * 20]; | |
302 | int q, res = 0; | |
303 | ||
304 | spin_lock_irqsave(&local->queue_stop_reason_lock, flags); | |
305 | for (q = 0; q < local->hw.queues; q++) | |
306 | res += sprintf(buf + res, "%02d: %#.8lx/%d\n", q, | |
307 | local->queue_stop_reasons[q], | |
3b8d81e0 | 308 | skb_queue_len(&local->pending[q])); |
db2e6bd4 JB |
309 | spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags); |
310 | ||
311 | return simple_read_from_buffer(user_buf, count, ppos, buf, res); | |
312 | } | |
313 | ||
314 | static const struct file_operations queues_ops = { | |
315 | .read = queues_read, | |
6038f373 AB |
316 | .open = mac80211_open_file_generic, |
317 | .llseek = default_llseek, | |
db2e6bd4 JB |
318 | }; |
319 | ||
e9f207f0 JB |
320 | /* statistics stuff */ |
321 | ||
e9f207f0 JB |
322 | static ssize_t format_devstat_counter(struct ieee80211_local *local, |
323 | char __user *userbuf, | |
324 | size_t count, loff_t *ppos, | |
325 | int (*printvalue)(struct ieee80211_low_level_stats *stats, char *buf, | |
326 | int buflen)) | |
327 | { | |
328 | struct ieee80211_low_level_stats stats; | |
329 | char buf[20]; | |
330 | int res; | |
331 | ||
75636525 | 332 | rtnl_lock(); |
24487981 | 333 | res = drv_get_stats(local, &stats); |
e9f207f0 | 334 | rtnl_unlock(); |
24487981 JB |
335 | if (res) |
336 | return res; | |
337 | res = printvalue(&stats, buf, sizeof(buf)); | |
e9f207f0 JB |
338 | return simple_read_from_buffer(userbuf, count, ppos, buf, res); |
339 | } | |
340 | ||
341 | #define DEBUGFS_DEVSTATS_FILE(name) \ | |
342 | static int print_devstats_##name(struct ieee80211_low_level_stats *stats,\ | |
343 | char *buf, int buflen) \ | |
344 | { \ | |
345 | return scnprintf(buf, buflen, "%u\n", stats->name); \ | |
346 | } \ | |
347 | static ssize_t stats_ ##name## _read(struct file *file, \ | |
348 | char __user *userbuf, \ | |
349 | size_t count, loff_t *ppos) \ | |
350 | { \ | |
351 | return format_devstat_counter(file->private_data, \ | |
352 | userbuf, \ | |
353 | count, \ | |
354 | ppos, \ | |
355 | print_devstats_##name); \ | |
356 | } \ | |
357 | \ | |
358 | static const struct file_operations stats_ ##name## _ops = { \ | |
359 | .read = stats_ ##name## _read, \ | |
360 | .open = mac80211_open_file_generic, \ | |
2b18ab36 | 361 | .llseek = generic_file_llseek, \ |
e9f207f0 JB |
362 | }; |
363 | ||
2826bcd8 FF |
364 | #define DEBUGFS_STATS_ADD(name, field) \ |
365 | debugfs_create_u32(#name, 0400, statsd, (u32 *) &field); | |
366 | #define DEBUGFS_DEVSTATS_ADD(name) \ | |
7bcfaf2f | 367 | debugfs_create_file(#name, 0400, statsd, local, &stats_ ##name## _ops); |
e9f207f0 | 368 | |
e9f207f0 JB |
369 | DEBUGFS_DEVSTATS_FILE(dot11ACKFailureCount); |
370 | DEBUGFS_DEVSTATS_FILE(dot11RTSFailureCount); | |
371 | DEBUGFS_DEVSTATS_FILE(dot11FCSErrorCount); | |
372 | DEBUGFS_DEVSTATS_FILE(dot11RTSSuccessCount); | |
373 | ||
e9f207f0 JB |
374 | void debugfs_hw_add(struct ieee80211_local *local) |
375 | { | |
376 | struct dentry *phyd = local->hw.wiphy->debugfsdir; | |
377 | struct dentry *statsd; | |
378 | ||
379 | if (!phyd) | |
380 | return; | |
381 | ||
e9f207f0 JB |
382 | local->debugfs.keys = debugfs_create_dir("keys", phyd); |
383 | ||
e9f207f0 | 384 | DEBUGFS_ADD(frequency); |
e9f207f0 | 385 | DEBUGFS_ADD(total_ps_buffered); |
e9f207f0 | 386 | DEBUGFS_ADD(wep_iv); |
ae54c985 | 387 | DEBUGFS_ADD(tsf); |
db2e6bd4 | 388 | DEBUGFS_ADD(queues); |
827b1fb4 | 389 | DEBUGFS_ADD_MODE(reset, 0200); |
d3707d99 | 390 | DEBUGFS_ADD(noack); |
50ae0cf1 KV |
391 | DEBUGFS_ADD(uapsd_queues); |
392 | DEBUGFS_ADD(uapsd_max_sp_len); | |
199d69f2 | 393 | DEBUGFS_ADD(channel_type); |
e9f207f0 JB |
394 | |
395 | statsd = debugfs_create_dir("statistics", phyd); | |
e9f207f0 JB |
396 | |
397 | /* if the dir failed, don't put all the other things into the root! */ | |
398 | if (!statsd) | |
399 | return; | |
400 | ||
2826bcd8 FF |
401 | DEBUGFS_STATS_ADD(transmitted_fragment_count, |
402 | local->dot11TransmittedFragmentCount); | |
403 | DEBUGFS_STATS_ADD(multicast_transmitted_frame_count, | |
404 | local->dot11MulticastTransmittedFrameCount); | |
405 | DEBUGFS_STATS_ADD(failed_count, local->dot11FailedCount); | |
406 | DEBUGFS_STATS_ADD(retry_count, local->dot11RetryCount); | |
407 | DEBUGFS_STATS_ADD(multiple_retry_count, | |
408 | local->dot11MultipleRetryCount); | |
409 | DEBUGFS_STATS_ADD(frame_duplicate_count, | |
410 | local->dot11FrameDuplicateCount); | |
411 | DEBUGFS_STATS_ADD(received_fragment_count, | |
412 | local->dot11ReceivedFragmentCount); | |
413 | DEBUGFS_STATS_ADD(multicast_received_frame_count, | |
414 | local->dot11MulticastReceivedFrameCount); | |
415 | DEBUGFS_STATS_ADD(transmitted_frame_count, | |
416 | local->dot11TransmittedFrameCount); | |
e9f207f0 | 417 | #ifdef CONFIG_MAC80211_DEBUG_COUNTERS |
2826bcd8 FF |
418 | DEBUGFS_STATS_ADD(tx_handlers_drop, local->tx_handlers_drop); |
419 | DEBUGFS_STATS_ADD(tx_handlers_queued, local->tx_handlers_queued); | |
420 | DEBUGFS_STATS_ADD(tx_handlers_drop_unencrypted, | |
421 | local->tx_handlers_drop_unencrypted); | |
422 | DEBUGFS_STATS_ADD(tx_handlers_drop_fragment, | |
423 | local->tx_handlers_drop_fragment); | |
424 | DEBUGFS_STATS_ADD(tx_handlers_drop_wep, | |
425 | local->tx_handlers_drop_wep); | |
426 | DEBUGFS_STATS_ADD(tx_handlers_drop_not_assoc, | |
427 | local->tx_handlers_drop_not_assoc); | |
428 | DEBUGFS_STATS_ADD(tx_handlers_drop_unauth_port, | |
429 | local->tx_handlers_drop_unauth_port); | |
430 | DEBUGFS_STATS_ADD(rx_handlers_drop, local->rx_handlers_drop); | |
431 | DEBUGFS_STATS_ADD(rx_handlers_queued, local->rx_handlers_queued); | |
432 | DEBUGFS_STATS_ADD(rx_handlers_drop_nullfunc, | |
433 | local->rx_handlers_drop_nullfunc); | |
434 | DEBUGFS_STATS_ADD(rx_handlers_drop_defrag, | |
435 | local->rx_handlers_drop_defrag); | |
436 | DEBUGFS_STATS_ADD(rx_handlers_drop_short, | |
437 | local->rx_handlers_drop_short); | |
438 | DEBUGFS_STATS_ADD(rx_handlers_drop_passive_scan, | |
439 | local->rx_handlers_drop_passive_scan); | |
440 | DEBUGFS_STATS_ADD(tx_expand_skb_head, | |
441 | local->tx_expand_skb_head); | |
442 | DEBUGFS_STATS_ADD(tx_expand_skb_head_cloned, | |
443 | local->tx_expand_skb_head_cloned); | |
444 | DEBUGFS_STATS_ADD(rx_expand_skb_head, | |
445 | local->rx_expand_skb_head); | |
446 | DEBUGFS_STATS_ADD(rx_expand_skb_head2, | |
447 | local->rx_expand_skb_head2); | |
448 | DEBUGFS_STATS_ADD(rx_handlers_fragments, | |
449 | local->rx_handlers_fragments); | |
450 | DEBUGFS_STATS_ADD(tx_status_drop, | |
451 | local->tx_status_drop); | |
e9f207f0 | 452 | #endif |
2826bcd8 FF |
453 | DEBUGFS_DEVSTATS_ADD(dot11ACKFailureCount); |
454 | DEBUGFS_DEVSTATS_ADD(dot11RTSFailureCount); | |
455 | DEBUGFS_DEVSTATS_ADD(dot11FCSErrorCount); | |
456 | DEBUGFS_DEVSTATS_ADD(dot11RTSSuccessCount); | |
e9f207f0 | 457 | } |