Commit | Line | Data |
---|---|---|
e9f207f0 JB |
1 | /* |
2 | * Copyright (c) 2006 Jiri Benc <jbenc@suse.cz> | |
3 | * Copyright 2007 Johannes Berg <johannes@sipsolutions.net> | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | */ | |
9 | ||
10 | #include <linux/kernel.h> | |
11 | #include <linux/device.h> | |
12 | #include <linux/if.h> | |
13 | #include <linux/interrupt.h> | |
14 | #include <linux/netdevice.h> | |
15 | #include <linux/rtnetlink.h> | |
16 | #include <linux/notifier.h> | |
17 | #include <net/mac80211.h> | |
18 | #include <net/cfg80211.h> | |
19 | #include "ieee80211_i.h" | |
2c8dccc7 | 20 | #include "rate.h" |
e9f207f0 JB |
21 | #include "debugfs.h" |
22 | #include "debugfs_netdev.h" | |
23 | ||
24 | static ssize_t ieee80211_if_read( | |
25 | struct ieee80211_sub_if_data *sdata, | |
26 | char __user *userbuf, | |
27 | size_t count, loff_t *ppos, | |
28 | ssize_t (*format)(const struct ieee80211_sub_if_data *, char *, int)) | |
29 | { | |
30 | char buf[70]; | |
31 | ssize_t ret = -EINVAL; | |
32 | ||
33 | read_lock(&dev_base_lock); | |
73bb3e4a | 34 | if (sdata->dev->reg_state == NETREG_REGISTERED) |
e9f207f0 | 35 | ret = (*format)(sdata, buf, sizeof(buf)); |
e9f207f0 | 36 | read_unlock(&dev_base_lock); |
73bb3e4a LCC |
37 | |
38 | if (ret != -EINVAL) | |
39 | ret = simple_read_from_buffer(userbuf, count, ppos, buf, ret); | |
40 | ||
e9f207f0 JB |
41 | return ret; |
42 | } | |
43 | ||
44 | #define IEEE80211_IF_FMT(name, field, format_string) \ | |
45 | static ssize_t ieee80211_if_fmt_##name( \ | |
46 | const struct ieee80211_sub_if_data *sdata, char *buf, \ | |
47 | int buflen) \ | |
48 | { \ | |
49 | return scnprintf(buf, buflen, format_string, sdata->field); \ | |
50 | } | |
51 | #define IEEE80211_IF_FMT_DEC(name, field) \ | |
52 | IEEE80211_IF_FMT(name, field, "%d\n") | |
53 | #define IEEE80211_IF_FMT_HEX(name, field) \ | |
54 | IEEE80211_IF_FMT(name, field, "%#x\n") | |
55 | #define IEEE80211_IF_FMT_SIZE(name, field) \ | |
56 | IEEE80211_IF_FMT(name, field, "%zd\n") | |
57 | ||
58 | #define IEEE80211_IF_FMT_ATOMIC(name, field) \ | |
59 | static ssize_t ieee80211_if_fmt_##name( \ | |
60 | const struct ieee80211_sub_if_data *sdata, \ | |
61 | char *buf, int buflen) \ | |
62 | { \ | |
63 | return scnprintf(buf, buflen, "%d\n", atomic_read(&sdata->field));\ | |
64 | } | |
65 | ||
66 | #define IEEE80211_IF_FMT_MAC(name, field) \ | |
67 | static ssize_t ieee80211_if_fmt_##name( \ | |
68 | const struct ieee80211_sub_if_data *sdata, char *buf, \ | |
69 | int buflen) \ | |
70 | { \ | |
0c68ae26 | 71 | return scnprintf(buf, buflen, "%pM\n", sdata->field); \ |
e9f207f0 JB |
72 | } |
73 | ||
74 | #define __IEEE80211_IF_FILE(name) \ | |
75 | static ssize_t ieee80211_if_read_##name(struct file *file, \ | |
76 | char __user *userbuf, \ | |
77 | size_t count, loff_t *ppos) \ | |
78 | { \ | |
79 | return ieee80211_if_read(file->private_data, \ | |
80 | userbuf, count, ppos, \ | |
81 | ieee80211_if_fmt_##name); \ | |
82 | } \ | |
83 | static const struct file_operations name##_ops = { \ | |
84 | .read = ieee80211_if_read_##name, \ | |
85 | .open = mac80211_open_file_generic, \ | |
86 | } | |
87 | ||
88 | #define IEEE80211_IF_FILE(name, field, format) \ | |
89 | IEEE80211_IF_FMT_##format(name, field) \ | |
90 | __IEEE80211_IF_FILE(name) | |
91 | ||
92 | /* common attributes */ | |
e9f207f0 | 93 | IEEE80211_IF_FILE(drop_unencrypted, drop_unencrypted, DEC); |
3e122be0 JB |
94 | IEEE80211_IF_FILE(force_unicast_rateidx, force_unicast_rateidx, DEC); |
95 | IEEE80211_IF_FILE(max_ratectrl_rateidx, max_ratectrl_rateidx, DEC); | |
e9f207f0 | 96 | |
46900298 | 97 | /* STA attributes */ |
46900298 | 98 | IEEE80211_IF_FILE(bssid, u.mgd.bssid, MAC); |
46900298 | 99 | IEEE80211_IF_FILE(aid, u.mgd.aid, DEC); |
46900298 | 100 | IEEE80211_IF_FILE(capab, u.mgd.capab, HEX); |
e9f207f0 JB |
101 | |
102 | /* AP attributes */ | |
103 | IEEE80211_IF_FILE(num_sta_ps, u.ap.num_sta_ps, ATOMIC); | |
e9f207f0 | 104 | IEEE80211_IF_FILE(dtim_count, u.ap.dtim_count, DEC); |
e9f207f0 JB |
105 | |
106 | static ssize_t ieee80211_if_fmt_num_buffered_multicast( | |
107 | const struct ieee80211_sub_if_data *sdata, char *buf, int buflen) | |
108 | { | |
109 | return scnprintf(buf, buflen, "%u\n", | |
110 | skb_queue_len(&sdata->u.ap.ps_bc_buf)); | |
111 | } | |
112 | __IEEE80211_IF_FILE(num_buffered_multicast); | |
113 | ||
e9f207f0 JB |
114 | /* WDS attributes */ |
115 | IEEE80211_IF_FILE(peer, u.wds.remote_addr, MAC); | |
116 | ||
9f42f607 LCC |
117 | #ifdef CONFIG_MAC80211_MESH |
118 | /* Mesh stats attributes */ | |
c8a61a7d DW |
119 | IEEE80211_IF_FILE(fwded_mcast, u.mesh.mshstats.fwded_mcast, DEC); |
120 | IEEE80211_IF_FILE(fwded_unicast, u.mesh.mshstats.fwded_unicast, DEC); | |
472dbc45 JB |
121 | IEEE80211_IF_FILE(fwded_frames, u.mesh.mshstats.fwded_frames, DEC); |
122 | IEEE80211_IF_FILE(dropped_frames_ttl, u.mesh.mshstats.dropped_frames_ttl, DEC); | |
9f42f607 | 123 | IEEE80211_IF_FILE(dropped_frames_no_route, |
472dbc45 JB |
124 | u.mesh.mshstats.dropped_frames_no_route, DEC); |
125 | IEEE80211_IF_FILE(estab_plinks, u.mesh.mshstats.estab_plinks, ATOMIC); | |
9f42f607 LCC |
126 | |
127 | /* Mesh parameters */ | |
36ff382d JB |
128 | IEEE80211_IF_FILE(dot11MeshMaxRetries, |
129 | u.mesh.mshcfg.dot11MeshMaxRetries, DEC); | |
130 | IEEE80211_IF_FILE(dot11MeshRetryTimeout, | |
131 | u.mesh.mshcfg.dot11MeshRetryTimeout, DEC); | |
132 | IEEE80211_IF_FILE(dot11MeshConfirmTimeout, | |
133 | u.mesh.mshcfg.dot11MeshConfirmTimeout, DEC); | |
134 | IEEE80211_IF_FILE(dot11MeshHoldingTimeout, | |
135 | u.mesh.mshcfg.dot11MeshHoldingTimeout, DEC); | |
136 | IEEE80211_IF_FILE(dot11MeshTTL, u.mesh.mshcfg.dot11MeshTTL, DEC); | |
137 | IEEE80211_IF_FILE(auto_open_plinks, u.mesh.mshcfg.auto_open_plinks, DEC); | |
138 | IEEE80211_IF_FILE(dot11MeshMaxPeerLinks, | |
139 | u.mesh.mshcfg.dot11MeshMaxPeerLinks, DEC); | |
140 | IEEE80211_IF_FILE(dot11MeshHWMPactivePathTimeout, | |
141 | u.mesh.mshcfg.dot11MeshHWMPactivePathTimeout, DEC); | |
142 | IEEE80211_IF_FILE(dot11MeshHWMPpreqMinInterval, | |
143 | u.mesh.mshcfg.dot11MeshHWMPpreqMinInterval, DEC); | |
144 | IEEE80211_IF_FILE(dot11MeshHWMPnetDiameterTraversalTime, | |
145 | u.mesh.mshcfg.dot11MeshHWMPnetDiameterTraversalTime, DEC); | |
146 | IEEE80211_IF_FILE(dot11MeshHWMPmaxPREQretries, | |
147 | u.mesh.mshcfg.dot11MeshHWMPmaxPREQretries, DEC); | |
148 | IEEE80211_IF_FILE(path_refresh_time, | |
149 | u.mesh.mshcfg.path_refresh_time, DEC); | |
150 | IEEE80211_IF_FILE(min_discovery_timeout, | |
151 | u.mesh.mshcfg.min_discovery_timeout, DEC); | |
63c5723b RP |
152 | IEEE80211_IF_FILE(dot11MeshHWMPRootMode, |
153 | u.mesh.mshcfg.dot11MeshHWMPRootMode, DEC); | |
9f42f607 LCC |
154 | #endif |
155 | ||
156 | ||
7bcfaf2f JB |
157 | #define DEBUGFS_ADD(name, type) \ |
158 | debugfs_create_file(#name, 0400, sdata->debugfs.dir, \ | |
159 | sdata, &name##_ops); | |
e9f207f0 JB |
160 | |
161 | static void add_sta_files(struct ieee80211_sub_if_data *sdata) | |
162 | { | |
e9f207f0 | 163 | DEBUGFS_ADD(drop_unencrypted, sta); |
93015f0f JM |
164 | DEBUGFS_ADD(force_unicast_rateidx, sta); |
165 | DEBUGFS_ADD(max_ratectrl_rateidx, sta); | |
3e122be0 | 166 | |
e9f207f0 | 167 | DEBUGFS_ADD(bssid, sta); |
e9f207f0 | 168 | DEBUGFS_ADD(aid, sta); |
e9f207f0 | 169 | DEBUGFS_ADD(capab, sta); |
e9f207f0 JB |
170 | } |
171 | ||
172 | static void add_ap_files(struct ieee80211_sub_if_data *sdata) | |
173 | { | |
e9f207f0 | 174 | DEBUGFS_ADD(drop_unencrypted, ap); |
3e122be0 JB |
175 | DEBUGFS_ADD(force_unicast_rateidx, ap); |
176 | DEBUGFS_ADD(max_ratectrl_rateidx, ap); | |
177 | ||
e9f207f0 | 178 | DEBUGFS_ADD(num_sta_ps, ap); |
e9f207f0 | 179 | DEBUGFS_ADD(dtim_count, ap); |
e9f207f0 | 180 | DEBUGFS_ADD(num_buffered_multicast, ap); |
e9f207f0 JB |
181 | } |
182 | ||
183 | static void add_wds_files(struct ieee80211_sub_if_data *sdata) | |
184 | { | |
e9f207f0 | 185 | DEBUGFS_ADD(drop_unencrypted, wds); |
93015f0f JM |
186 | DEBUGFS_ADD(force_unicast_rateidx, wds); |
187 | DEBUGFS_ADD(max_ratectrl_rateidx, wds); | |
3e122be0 | 188 | |
e9f207f0 JB |
189 | DEBUGFS_ADD(peer, wds); |
190 | } | |
191 | ||
192 | static void add_vlan_files(struct ieee80211_sub_if_data *sdata) | |
193 | { | |
e9f207f0 | 194 | DEBUGFS_ADD(drop_unencrypted, vlan); |
93015f0f JM |
195 | DEBUGFS_ADD(force_unicast_rateidx, vlan); |
196 | DEBUGFS_ADD(max_ratectrl_rateidx, vlan); | |
e9f207f0 JB |
197 | } |
198 | ||
199 | static void add_monitor_files(struct ieee80211_sub_if_data *sdata) | |
200 | { | |
e9f207f0 JB |
201 | } |
202 | ||
9f42f607 | 203 | #ifdef CONFIG_MAC80211_MESH |
9f42f607 LCC |
204 | |
205 | static void add_mesh_stats(struct ieee80211_sub_if_data *sdata) | |
206 | { | |
7bcfaf2f JB |
207 | struct dentry *dir = debugfs_create_dir("mesh_stats", |
208 | sdata->debugfs.dir); | |
209 | ||
210 | #define MESHSTATS_ADD(name)\ | |
211 | debugfs_create_file(#name, 0400, dir, sdata, &name##_ops); | |
212 | ||
c8a61a7d DW |
213 | MESHSTATS_ADD(fwded_mcast); |
214 | MESHSTATS_ADD(fwded_unicast); | |
9f42f607 LCC |
215 | MESHSTATS_ADD(fwded_frames); |
216 | MESHSTATS_ADD(dropped_frames_ttl); | |
217 | MESHSTATS_ADD(dropped_frames_no_route); | |
218 | MESHSTATS_ADD(estab_plinks); | |
7bcfaf2f | 219 | #undef MESHSTATS_ADD |
9f42f607 LCC |
220 | } |
221 | ||
9f42f607 LCC |
222 | static void add_mesh_config(struct ieee80211_sub_if_data *sdata) |
223 | { | |
7bcfaf2f JB |
224 | struct dentry *dir = debugfs_create_dir("mesh_config", |
225 | sdata->debugfs.dir); | |
226 | ||
227 | #define MESHPARAMS_ADD(name) \ | |
228 | debugfs_create_file(#name, 0600, dir, sdata, &name##_ops); | |
229 | ||
9f42f607 LCC |
230 | MESHPARAMS_ADD(dot11MeshMaxRetries); |
231 | MESHPARAMS_ADD(dot11MeshRetryTimeout); | |
232 | MESHPARAMS_ADD(dot11MeshConfirmTimeout); | |
233 | MESHPARAMS_ADD(dot11MeshHoldingTimeout); | |
234 | MESHPARAMS_ADD(dot11MeshTTL); | |
235 | MESHPARAMS_ADD(auto_open_plinks); | |
236 | MESHPARAMS_ADD(dot11MeshMaxPeerLinks); | |
237 | MESHPARAMS_ADD(dot11MeshHWMPactivePathTimeout); | |
238 | MESHPARAMS_ADD(dot11MeshHWMPpreqMinInterval); | |
239 | MESHPARAMS_ADD(dot11MeshHWMPnetDiameterTraversalTime); | |
240 | MESHPARAMS_ADD(dot11MeshHWMPmaxPREQretries); | |
241 | MESHPARAMS_ADD(path_refresh_time); | |
242 | MESHPARAMS_ADD(min_discovery_timeout); | |
7bcfaf2f JB |
243 | |
244 | #undef MESHPARAMS_ADD | |
9f42f607 LCC |
245 | } |
246 | #endif | |
247 | ||
e9f207f0 JB |
248 | static void add_files(struct ieee80211_sub_if_data *sdata) |
249 | { | |
7bcfaf2f | 250 | if (!sdata->debugfs.dir) |
e9f207f0 JB |
251 | return; |
252 | ||
51fb61e7 | 253 | switch (sdata->vif.type) { |
05c914fe | 254 | case NL80211_IFTYPE_MESH_POINT: |
9f42f607 LCC |
255 | #ifdef CONFIG_MAC80211_MESH |
256 | add_mesh_stats(sdata); | |
257 | add_mesh_config(sdata); | |
258 | #endif | |
472dbc45 | 259 | break; |
05c914fe | 260 | case NL80211_IFTYPE_STATION: |
e9f207f0 JB |
261 | add_sta_files(sdata); |
262 | break; | |
46900298 JB |
263 | case NL80211_IFTYPE_ADHOC: |
264 | /* XXX */ | |
265 | break; | |
05c914fe | 266 | case NL80211_IFTYPE_AP: |
e9f207f0 JB |
267 | add_ap_files(sdata); |
268 | break; | |
05c914fe | 269 | case NL80211_IFTYPE_WDS: |
e9f207f0 JB |
270 | add_wds_files(sdata); |
271 | break; | |
05c914fe | 272 | case NL80211_IFTYPE_MONITOR: |
e9f207f0 JB |
273 | add_monitor_files(sdata); |
274 | break; | |
05c914fe | 275 | case NL80211_IFTYPE_AP_VLAN: |
e9f207f0 JB |
276 | add_vlan_files(sdata); |
277 | break; | |
278 | default: | |
279 | break; | |
280 | } | |
281 | } | |
282 | ||
e9f207f0 JB |
283 | static int notif_registered; |
284 | ||
285 | void ieee80211_debugfs_add_netdev(struct ieee80211_sub_if_data *sdata) | |
286 | { | |
287 | char buf[10+IFNAMSIZ]; | |
288 | ||
289 | if (!notif_registered) | |
290 | return; | |
291 | ||
292 | sprintf(buf, "netdev:%s", sdata->dev->name); | |
7bcfaf2f | 293 | sdata->debugfs.dir = debugfs_create_dir(buf, |
e9f207f0 | 294 | sdata->local->hw.wiphy->debugfsdir); |
75636525 | 295 | add_files(sdata); |
e9f207f0 JB |
296 | } |
297 | ||
298 | void ieee80211_debugfs_remove_netdev(struct ieee80211_sub_if_data *sdata) | |
299 | { | |
7bcfaf2f JB |
300 | if (!sdata->debugfs.dir) |
301 | return; | |
302 | ||
303 | debugfs_remove_recursive(sdata->debugfs.dir); | |
304 | sdata->debugfs.dir = NULL; | |
e9f207f0 JB |
305 | } |
306 | ||
988c0f72 | 307 | static int netdev_notify(struct notifier_block *nb, |
e9f207f0 JB |
308 | unsigned long state, |
309 | void *ndev) | |
310 | { | |
311 | struct net_device *dev = ndev; | |
7c8081eb | 312 | struct dentry *dir; |
3e122be0 | 313 | struct ieee80211_sub_if_data *sdata; |
e9f207f0 JB |
314 | char buf[10+IFNAMSIZ]; |
315 | ||
316 | if (state != NETDEV_CHANGENAME) | |
317 | return 0; | |
318 | ||
319 | if (!dev->ieee80211_ptr || !dev->ieee80211_ptr->wiphy) | |
320 | return 0; | |
321 | ||
322 | if (dev->ieee80211_ptr->wiphy->privid != mac80211_wiphy_privid) | |
323 | return 0; | |
324 | ||
3e122be0 JB |
325 | sdata = IEEE80211_DEV_TO_SUB_IF(dev); |
326 | ||
7bcfaf2f | 327 | dir = sdata->debugfs.dir; |
c74e90a9 JB |
328 | |
329 | if (!dir) | |
330 | return 0; | |
331 | ||
332 | sprintf(buf, "netdev:%s", dev->name); | |
7c8081eb JB |
333 | if (!debugfs_rename(dir->d_parent, dir, dir->d_parent, buf)) |
334 | printk(KERN_ERR "mac80211: debugfs: failed to rename debugfs " | |
335 | "dir to %s\n", buf); | |
e9f207f0 JB |
336 | |
337 | return 0; | |
338 | } | |
339 | ||
340 | static struct notifier_block mac80211_debugfs_netdev_notifier = { | |
341 | .notifier_call = netdev_notify, | |
342 | }; | |
343 | ||
344 | void ieee80211_debugfs_netdev_init(void) | |
345 | { | |
346 | int err; | |
347 | ||
348 | err = register_netdevice_notifier(&mac80211_debugfs_netdev_notifier); | |
349 | if (err) { | |
350 | printk(KERN_ERR | |
351 | "mac80211: failed to install netdev notifier," | |
352 | " disabling per-netdev debugfs!\n"); | |
353 | } else | |
354 | notif_registered = 1; | |
355 | } | |
356 | ||
357 | void ieee80211_debugfs_netdev_exit(void) | |
358 | { | |
359 | unregister_netdevice_notifier(&mac80211_debugfs_netdev_notifier); | |
360 | notif_registered = 0; | |
361 | } |