]>
Commit | Line | Data |
---|---|---|
e4d6b795 MB |
1 | /* |
2 | ||
3 | Broadcom B43 wireless driver | |
4 | ||
5 | debugfs driver debugging code | |
6 | ||
7 | Copyright (c) 2005-2007 Michael Buesch <mb@bu3sch.de> | |
8 | ||
9 | This program is free software; you can redistribute it and/or modify | |
10 | it under the terms of the GNU General Public License as published by | |
11 | the Free Software Foundation; either version 2 of the License, or | |
12 | (at your option) any later version. | |
13 | ||
14 | This program is distributed in the hope that it will be useful, | |
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | GNU General Public License for more details. | |
18 | ||
19 | You should have received a copy of the GNU General Public License | |
20 | along with this program; see the file COPYING. If not, write to | |
21 | the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, | |
22 | Boston, MA 02110-1301, USA. | |
23 | ||
24 | */ | |
25 | ||
26 | #include <linux/fs.h> | |
27 | #include <linux/debugfs.h> | |
28 | #include <linux/slab.h> | |
29 | #include <linux/netdevice.h> | |
30 | #include <linux/pci.h> | |
31 | #include <linux/mutex.h> | |
32 | ||
33 | #include "b43.h" | |
34 | #include "main.h" | |
35 | #include "debugfs.h" | |
36 | #include "dma.h" | |
e4d6b795 MB |
37 | #include "xmit.h" |
38 | ||
39 | ||
40 | /* The root directory. */ | |
1a09404a | 41 | static struct dentry *rootdir; |
e4d6b795 MB |
42 | |
43 | struct b43_debugfs_fops { | |
44 | ssize_t (*read)(struct b43_wldev *dev, char *buf, size_t bufsize); | |
45 | int (*write)(struct b43_wldev *dev, const char *buf, size_t count); | |
46 | struct file_operations fops; | |
47 | /* Offset of struct b43_dfs_file in struct b43_dfsentry */ | |
48 | size_t file_struct_offset; | |
49 | /* Take wl->irq_lock before calling read/write? */ | |
50 | bool take_irqlock; | |
51 | }; | |
52 | ||
53 | static inline | |
54 | struct b43_dfs_file * fops_to_dfs_file(struct b43_wldev *dev, | |
55 | const struct b43_debugfs_fops *dfops) | |
56 | { | |
57 | void *p; | |
58 | ||
59 | p = dev->dfsentry; | |
60 | p += dfops->file_struct_offset; | |
61 | ||
62 | return p; | |
63 | } | |
64 | ||
65 | ||
66 | #define fappend(fmt, x...) \ | |
67 | do { \ | |
68 | if (bufsize - count) \ | |
69 | count += snprintf(buf + count, \ | |
70 | bufsize - count, \ | |
71 | fmt , ##x); \ | |
72 | else \ | |
73 | printk(KERN_ERR "b43: fappend overflow\n"); \ | |
74 | } while (0) | |
75 | ||
76 | ||
6bbc321a MB |
77 | /* The biggest address values for SHM access from the debugfs files. */ |
78 | #define B43_MAX_SHM_ROUTING 4 | |
79 | #define B43_MAX_SHM_ADDR 0xFFFF | |
80 | ||
81 | static ssize_t shm16read__read_file(struct b43_wldev *dev, | |
82 | char *buf, size_t bufsize) | |
83 | { | |
84 | ssize_t count = 0; | |
85 | unsigned int routing, addr; | |
86 | u16 val; | |
87 | ||
88 | routing = dev->dfsentry->shm16read_routing_next; | |
89 | addr = dev->dfsentry->shm16read_addr_next; | |
90 | if ((routing > B43_MAX_SHM_ROUTING) || | |
91 | (addr > B43_MAX_SHM_ADDR)) | |
92 | return -EDESTADDRREQ; | |
93 | ||
94 | val = b43_shm_read16(dev, routing, addr); | |
95 | fappend("0x%04X\n", val); | |
96 | ||
97 | return count; | |
98 | } | |
99 | ||
100 | static int shm16read__write_file(struct b43_wldev *dev, | |
101 | const char *buf, size_t count) | |
102 | { | |
103 | unsigned int routing, addr; | |
104 | int res; | |
105 | ||
106 | res = sscanf(buf, "0x%X 0x%X", &routing, &addr); | |
107 | if (res != 2) | |
108 | return -EINVAL; | |
109 | if (routing > B43_MAX_SHM_ROUTING) | |
110 | return -EADDRNOTAVAIL; | |
111 | if (addr > B43_MAX_SHM_ADDR) | |
112 | return -EADDRNOTAVAIL; | |
113 | if (routing == B43_SHM_SHARED) { | |
114 | if ((addr % 2) != 0) | |
115 | return -EADDRNOTAVAIL; | |
116 | } | |
117 | ||
118 | dev->dfsentry->shm16read_routing_next = routing; | |
119 | dev->dfsentry->shm16read_addr_next = addr; | |
120 | ||
121 | return 0; | |
122 | } | |
123 | ||
124 | static int shm16write__write_file(struct b43_wldev *dev, | |
125 | const char *buf, size_t count) | |
126 | { | |
127 | unsigned int routing, addr, mask, set; | |
128 | u16 val; | |
129 | int res; | |
130 | unsigned long flags; | |
131 | ||
132 | res = sscanf(buf, "0x%X 0x%X 0x%X 0x%X", | |
133 | &routing, &addr, &mask, &set); | |
134 | if (res != 4) | |
135 | return -EINVAL; | |
136 | if (routing > B43_MAX_SHM_ROUTING) | |
137 | return -EADDRNOTAVAIL; | |
138 | if (addr > B43_MAX_SHM_ADDR) | |
139 | return -EADDRNOTAVAIL; | |
140 | if (routing == B43_SHM_SHARED) { | |
141 | if ((addr % 2) != 0) | |
142 | return -EADDRNOTAVAIL; | |
143 | } | |
144 | if ((mask > 0xFFFF) || (set > 0xFFFF)) | |
145 | return -E2BIG; | |
146 | ||
147 | spin_lock_irqsave(&dev->wl->shm_lock, flags); | |
148 | if (mask == 0) | |
149 | val = 0; | |
150 | else | |
151 | val = __b43_shm_read16(dev, routing, addr); | |
152 | val &= mask; | |
153 | val |= set; | |
154 | __b43_shm_write16(dev, routing, addr, val); | |
155 | spin_unlock_irqrestore(&dev->wl->shm_lock, flags); | |
156 | ||
157 | return 0; | |
158 | } | |
159 | ||
160 | static ssize_t shm32read__read_file(struct b43_wldev *dev, | |
161 | char *buf, size_t bufsize) | |
162 | { | |
163 | ssize_t count = 0; | |
164 | unsigned int routing, addr; | |
165 | u32 val; | |
166 | ||
167 | routing = dev->dfsentry->shm32read_routing_next; | |
168 | addr = dev->dfsentry->shm32read_addr_next; | |
169 | if ((routing > B43_MAX_SHM_ROUTING) || | |
170 | (addr > B43_MAX_SHM_ADDR)) | |
171 | return -EDESTADDRREQ; | |
172 | ||
173 | val = b43_shm_read32(dev, routing, addr); | |
174 | fappend("0x%08X\n", val); | |
175 | ||
176 | return count; | |
177 | } | |
178 | ||
179 | static int shm32read__write_file(struct b43_wldev *dev, | |
180 | const char *buf, size_t count) | |
181 | { | |
182 | unsigned int routing, addr; | |
183 | int res; | |
184 | ||
185 | res = sscanf(buf, "0x%X 0x%X", &routing, &addr); | |
186 | if (res != 2) | |
187 | return -EINVAL; | |
188 | if (routing > B43_MAX_SHM_ROUTING) | |
189 | return -EADDRNOTAVAIL; | |
190 | if (addr > B43_MAX_SHM_ADDR) | |
191 | return -EADDRNOTAVAIL; | |
192 | if (routing == B43_SHM_SHARED) { | |
193 | if ((addr % 2) != 0) | |
194 | return -EADDRNOTAVAIL; | |
195 | } | |
196 | ||
197 | dev->dfsentry->shm32read_routing_next = routing; | |
198 | dev->dfsentry->shm32read_addr_next = addr; | |
199 | ||
200 | return 0; | |
201 | } | |
202 | ||
203 | static int shm32write__write_file(struct b43_wldev *dev, | |
204 | const char *buf, size_t count) | |
205 | { | |
206 | unsigned int routing, addr, mask, set; | |
207 | u32 val; | |
208 | int res; | |
209 | unsigned long flags; | |
210 | ||
211 | res = sscanf(buf, "0x%X 0x%X 0x%X 0x%X", | |
212 | &routing, &addr, &mask, &set); | |
213 | if (res != 4) | |
214 | return -EINVAL; | |
215 | if (routing > B43_MAX_SHM_ROUTING) | |
216 | return -EADDRNOTAVAIL; | |
217 | if (addr > B43_MAX_SHM_ADDR) | |
218 | return -EADDRNOTAVAIL; | |
219 | if (routing == B43_SHM_SHARED) { | |
220 | if ((addr % 2) != 0) | |
221 | return -EADDRNOTAVAIL; | |
222 | } | |
223 | if ((mask > 0xFFFFFFFF) || (set > 0xFFFFFFFF)) | |
224 | return -E2BIG; | |
225 | ||
226 | spin_lock_irqsave(&dev->wl->shm_lock, flags); | |
227 | if (mask == 0) | |
228 | val = 0; | |
229 | else | |
230 | val = __b43_shm_read32(dev, routing, addr); | |
231 | val &= mask; | |
232 | val |= set; | |
233 | __b43_shm_write32(dev, routing, addr, val); | |
234 | spin_unlock_irqrestore(&dev->wl->shm_lock, flags); | |
235 | ||
236 | return 0; | |
237 | } | |
238 | ||
8bd463f4 MB |
239 | /* The biggest MMIO address that we allow access to from the debugfs files. */ |
240 | #define B43_MAX_MMIO_ACCESS (0xF00 - 1) | |
241 | ||
242 | static ssize_t mmio16read__read_file(struct b43_wldev *dev, | |
243 | char *buf, size_t bufsize) | |
244 | { | |
245 | ssize_t count = 0; | |
246 | unsigned int addr; | |
247 | u16 val; | |
248 | ||
249 | addr = dev->dfsentry->mmio16read_next; | |
250 | if (addr > B43_MAX_MMIO_ACCESS) | |
251 | return -EDESTADDRREQ; | |
252 | ||
253 | val = b43_read16(dev, addr); | |
254 | fappend("0x%04X\n", val); | |
255 | ||
256 | return count; | |
257 | } | |
258 | ||
259 | static int mmio16read__write_file(struct b43_wldev *dev, | |
260 | const char *buf, size_t count) | |
261 | { | |
262 | unsigned int addr; | |
263 | int res; | |
264 | ||
265 | res = sscanf(buf, "0x%X", &addr); | |
266 | if (res != 1) | |
267 | return -EINVAL; | |
268 | if (addr > B43_MAX_MMIO_ACCESS) | |
269 | return -EADDRNOTAVAIL; | |
270 | ||
271 | dev->dfsentry->mmio16read_next = addr; | |
272 | ||
273 | return 0; | |
274 | } | |
275 | ||
276 | static int mmio16write__write_file(struct b43_wldev *dev, | |
277 | const char *buf, size_t count) | |
278 | { | |
279 | unsigned int addr, val; | |
280 | int res; | |
281 | ||
282 | res = sscanf(buf, "0x%X = 0x%X", &addr, &val); | |
283 | if (res != 2) | |
284 | return -EINVAL; | |
285 | if (addr > B43_MAX_MMIO_ACCESS) | |
286 | return -EADDRNOTAVAIL; | |
287 | if (val > 0xFFFF) | |
288 | return -E2BIG; | |
289 | ||
290 | b43_write16(dev, addr, val); | |
291 | ||
292 | return 0; | |
293 | } | |
294 | ||
295 | static ssize_t mmio32read__read_file(struct b43_wldev *dev, | |
296 | char *buf, size_t bufsize) | |
297 | { | |
298 | ssize_t count = 0; | |
299 | unsigned int addr; | |
300 | u32 val; | |
301 | ||
302 | addr = dev->dfsentry->mmio32read_next; | |
303 | if (addr > B43_MAX_MMIO_ACCESS) | |
304 | return -EDESTADDRREQ; | |
305 | ||
306 | val = b43_read32(dev, addr); | |
307 | fappend("0x%08X\n", val); | |
308 | ||
309 | return count; | |
310 | } | |
311 | ||
312 | static int mmio32read__write_file(struct b43_wldev *dev, | |
313 | const char *buf, size_t count) | |
314 | { | |
315 | unsigned int addr; | |
316 | int res; | |
317 | ||
318 | res = sscanf(buf, "0x%X", &addr); | |
319 | if (res != 1) | |
320 | return -EINVAL; | |
321 | if (addr > B43_MAX_MMIO_ACCESS) | |
322 | return -EADDRNOTAVAIL; | |
323 | ||
324 | dev->dfsentry->mmio32read_next = addr; | |
325 | ||
326 | return 0; | |
327 | } | |
328 | ||
329 | static int mmio32write__write_file(struct b43_wldev *dev, | |
330 | const char *buf, size_t count) | |
331 | { | |
332 | unsigned int addr, val; | |
333 | int res; | |
334 | ||
335 | res = sscanf(buf, "0x%X = 0x%X", &addr, &val); | |
336 | if (res != 2) | |
337 | return -EINVAL; | |
338 | if (addr > B43_MAX_MMIO_ACCESS) | |
339 | return -EADDRNOTAVAIL; | |
340 | if (val > 0xFFFFFFFF) | |
341 | return -E2BIG; | |
342 | ||
343 | b43_write32(dev, addr, val); | |
344 | ||
345 | return 0; | |
346 | } | |
347 | ||
e4d6b795 | 348 | /* wl->irq_lock is locked */ |
1a09404a MB |
349 | static ssize_t tsf_read_file(struct b43_wldev *dev, |
350 | char *buf, size_t bufsize) | |
e4d6b795 MB |
351 | { |
352 | ssize_t count = 0; | |
353 | u64 tsf; | |
354 | ||
355 | b43_tsf_read(dev, &tsf); | |
356 | fappend("0x%08x%08x\n", | |
357 | (unsigned int)((tsf & 0xFFFFFFFF00000000ULL) >> 32), | |
358 | (unsigned int)(tsf & 0xFFFFFFFFULL)); | |
359 | ||
360 | return count; | |
361 | } | |
362 | ||
363 | /* wl->irq_lock is locked */ | |
1a09404a MB |
364 | static int tsf_write_file(struct b43_wldev *dev, |
365 | const char *buf, size_t count) | |
e4d6b795 MB |
366 | { |
367 | u64 tsf; | |
368 | ||
369 | if (sscanf(buf, "%llu", (unsigned long long *)(&tsf)) != 1) | |
370 | return -EINVAL; | |
371 | b43_tsf_write(dev, tsf); | |
372 | ||
373 | return 0; | |
374 | } | |
375 | ||
376 | /* wl->irq_lock is locked */ | |
1a09404a MB |
377 | static ssize_t ucode_regs_read_file(struct b43_wldev *dev, |
378 | char *buf, size_t bufsize) | |
e4d6b795 MB |
379 | { |
380 | ssize_t count = 0; | |
381 | int i; | |
382 | ||
383 | for (i = 0; i < 64; i++) { | |
384 | fappend("r%d = 0x%04x\n", i, | |
385 | b43_shm_read16(dev, B43_SHM_SCRATCH, i)); | |
386 | } | |
387 | ||
388 | return count; | |
389 | } | |
390 | ||
391 | /* wl->irq_lock is locked */ | |
1a09404a MB |
392 | static ssize_t shm_read_file(struct b43_wldev *dev, |
393 | char *buf, size_t bufsize) | |
e4d6b795 MB |
394 | { |
395 | ssize_t count = 0; | |
396 | int i; | |
397 | u16 tmp; | |
398 | __le16 *le16buf = (__le16 *)buf; | |
399 | ||
400 | for (i = 0; i < 0x1000; i++) { | |
30c4ae42 | 401 | if (bufsize < sizeof(tmp)) |
e4d6b795 MB |
402 | break; |
403 | tmp = b43_shm_read16(dev, B43_SHM_SHARED, 2 * i); | |
404 | le16buf[i] = cpu_to_le16(tmp); | |
405 | count += sizeof(tmp); | |
406 | bufsize -= sizeof(tmp); | |
407 | } | |
408 | ||
409 | return count; | |
410 | } | |
411 | ||
1a09404a MB |
412 | static ssize_t txstat_read_file(struct b43_wldev *dev, |
413 | char *buf, size_t bufsize) | |
e4d6b795 MB |
414 | { |
415 | struct b43_txstatus_log *log = &dev->dfsentry->txstatlog; | |
416 | ssize_t count = 0; | |
417 | unsigned long flags; | |
418 | int i, idx; | |
419 | struct b43_txstatus *stat; | |
420 | ||
421 | spin_lock_irqsave(&log->lock, flags); | |
422 | if (log->end < 0) { | |
423 | fappend("Nothing transmitted, yet\n"); | |
424 | goto out_unlock; | |
425 | } | |
426 | fappend("b43 TX status reports:\n\n" | |
427 | "index | cookie | seq | phy_stat | frame_count | " | |
428 | "rts_count | supp_reason | pm_indicated | " | |
429 | "intermediate | for_ampdu | acked\n" "---\n"); | |
430 | i = log->end + 1; | |
431 | idx = 0; | |
432 | while (1) { | |
433 | if (i == B43_NR_LOGGED_TXSTATUS) | |
434 | i = 0; | |
435 | stat = &(log->log[i]); | |
436 | if (stat->cookie) { | |
437 | fappend("%03d | " | |
438 | "0x%04X | 0x%04X | 0x%02X | " | |
439 | "0x%X | 0x%X | " | |
440 | "%u | %u | " | |
441 | "%u | %u | %u\n", | |
442 | idx, | |
443 | stat->cookie, stat->seq, stat->phy_stat, | |
444 | stat->frame_count, stat->rts_count, | |
445 | stat->supp_reason, stat->pm_indicated, | |
446 | stat->intermediate, stat->for_ampdu, | |
447 | stat->acked); | |
448 | idx++; | |
449 | } | |
450 | if (i == log->end) | |
451 | break; | |
452 | i++; | |
453 | } | |
454 | out_unlock: | |
455 | spin_unlock_irqrestore(&log->lock, flags); | |
456 | ||
457 | return count; | |
458 | } | |
459 | ||
1a09404a MB |
460 | static ssize_t txpower_g_read_file(struct b43_wldev *dev, |
461 | char *buf, size_t bufsize) | |
e4d6b795 MB |
462 | { |
463 | ssize_t count = 0; | |
464 | ||
465 | if (dev->phy.type != B43_PHYTYPE_G) { | |
466 | fappend("Device is not a G-PHY\n"); | |
467 | goto out; | |
468 | } | |
469 | fappend("Control: %s\n", dev->phy.manual_txpower_control ? | |
470 | "MANUAL" : "AUTOMATIC"); | |
471 | fappend("Baseband attenuation: %u\n", dev->phy.bbatt.att); | |
472 | fappend("Radio attenuation: %u\n", dev->phy.rfatt.att); | |
473 | fappend("TX Mixer Gain: %s\n", | |
474 | (dev->phy.tx_control & B43_TXCTL_TXMIX) ? "ON" : "OFF"); | |
475 | fappend("PA Gain 2dB: %s\n", | |
476 | (dev->phy.tx_control & B43_TXCTL_PA2DB) ? "ON" : "OFF"); | |
477 | fappend("PA Gain 3dB: %s\n", | |
478 | (dev->phy.tx_control & B43_TXCTL_PA3DB) ? "ON" : "OFF"); | |
479 | fappend("\n\n"); | |
480 | fappend("You can write to this file:\n"); | |
481 | fappend("Writing \"auto\" enables automatic txpower control.\n"); | |
482 | fappend | |
483 | ("Writing the attenuation values as \"bbatt rfatt txmix pa2db pa3db\" " | |
484 | "enables manual txpower control.\n"); | |
485 | fappend("Example: 5 4 0 0 1\n"); | |
486 | fappend("Enables manual control with Baseband attenuation 5, " | |
487 | "Radio attenuation 4, No TX Mixer Gain, " | |
488 | "No PA Gain 2dB, With PA Gain 3dB.\n"); | |
489 | out: | |
490 | return count; | |
491 | } | |
492 | ||
1a09404a MB |
493 | static int txpower_g_write_file(struct b43_wldev *dev, |
494 | const char *buf, size_t count) | |
e4d6b795 | 495 | { |
b85b3b7a MB |
496 | if (dev->phy.type != B43_PHYTYPE_G) |
497 | return -ENODEV; | |
e4d6b795 MB |
498 | if ((count >= 4) && (memcmp(buf, "auto", 4) == 0)) { |
499 | /* Automatic control */ | |
500 | dev->phy.manual_txpower_control = 0; | |
501 | b43_phy_xmitpower(dev); | |
502 | } else { | |
503 | int bbatt = 0, rfatt = 0, txmix = 0, pa2db = 0, pa3db = 0; | |
504 | /* Manual control */ | |
505 | if (sscanf(buf, "%d %d %d %d %d", &bbatt, &rfatt, | |
b85b3b7a MB |
506 | &txmix, &pa2db, &pa3db) != 5) |
507 | return -EINVAL; | |
e4d6b795 MB |
508 | b43_put_attenuation_into_ranges(dev, &bbatt, &rfatt); |
509 | dev->phy.manual_txpower_control = 1; | |
510 | dev->phy.bbatt.att = bbatt; | |
511 | dev->phy.rfatt.att = rfatt; | |
512 | dev->phy.tx_control = 0; | |
513 | if (txmix) | |
514 | dev->phy.tx_control |= B43_TXCTL_TXMIX; | |
515 | if (pa2db) | |
516 | dev->phy.tx_control |= B43_TXCTL_PA2DB; | |
517 | if (pa3db) | |
518 | dev->phy.tx_control |= B43_TXCTL_PA3DB; | |
f31800d8 | 519 | b43_phy_lock(dev); |
e4d6b795 MB |
520 | b43_radio_lock(dev); |
521 | b43_set_txpower_g(dev, &dev->phy.bbatt, | |
522 | &dev->phy.rfatt, dev->phy.tx_control); | |
523 | b43_radio_unlock(dev); | |
f31800d8 | 524 | b43_phy_unlock(dev); |
e4d6b795 | 525 | } |
e4d6b795 | 526 | |
b85b3b7a | 527 | return 0; |
e4d6b795 MB |
528 | } |
529 | ||
530 | /* wl->irq_lock is locked */ | |
1a09404a MB |
531 | static int restart_write_file(struct b43_wldev *dev, |
532 | const char *buf, size_t count) | |
e4d6b795 MB |
533 | { |
534 | int err = 0; | |
535 | ||
536 | if (count > 0 && buf[0] == '1') { | |
537 | b43_controller_restart(dev, "manually restarted"); | |
538 | } else | |
539 | err = -EINVAL; | |
540 | ||
541 | return err; | |
542 | } | |
543 | ||
f5eda47f MB |
544 | static unsigned long calc_expire_secs(unsigned long now, |
545 | unsigned long time, | |
546 | unsigned long expire) | |
e4d6b795 | 547 | { |
f5eda47f MB |
548 | expire = time + expire; |
549 | ||
550 | if (time_after(now, expire)) | |
551 | return 0; /* expired */ | |
552 | if (expire < now) { | |
553 | /* jiffies wrapped */ | |
554 | expire -= MAX_JIFFY_OFFSET; | |
555 | now -= MAX_JIFFY_OFFSET; | |
e4d6b795 | 556 | } |
f5eda47f | 557 | B43_WARN_ON(expire < now); |
e4d6b795 | 558 | |
f5eda47f | 559 | return (expire - now) / HZ; |
e4d6b795 MB |
560 | } |
561 | ||
1a09404a MB |
562 | static ssize_t loctls_read_file(struct b43_wldev *dev, |
563 | char *buf, size_t bufsize) | |
e4d6b795 MB |
564 | { |
565 | ssize_t count = 0; | |
566 | struct b43_txpower_lo_control *lo; | |
567 | int i, err = 0; | |
f5eda47f MB |
568 | struct b43_lo_calib *cal; |
569 | unsigned long now = jiffies; | |
570 | struct b43_phy *phy = &dev->phy; | |
e4d6b795 | 571 | |
f5eda47f | 572 | if (phy->type != B43_PHYTYPE_G) { |
e4d6b795 MB |
573 | fappend("Device is not a G-PHY\n"); |
574 | err = -ENODEV; | |
575 | goto out; | |
576 | } | |
f5eda47f | 577 | lo = phy->lo_control; |
e4d6b795 | 578 | fappend("-- Local Oscillator calibration data --\n\n"); |
f5eda47f | 579 | fappend("HW-power-control enabled: %d\n", |
e4d6b795 | 580 | dev->phy.hardware_power_control); |
f5eda47f MB |
581 | fappend("TX Bias: 0x%02X, TX Magn: 0x%02X (expire in %lu sec)\n", |
582 | lo->tx_bias, lo->tx_magn, | |
583 | calc_expire_secs(now, lo->txctl_measured_time, | |
584 | B43_LO_TXCTL_EXPIRE)); | |
585 | fappend("Power Vector: 0x%08X%08X (expires in %lu sec)\n", | |
e4d6b795 | 586 | (unsigned int)((lo->power_vector & 0xFFFFFFFF00000000ULL) >> 32), |
f5eda47f MB |
587 | (unsigned int)(lo->power_vector & 0x00000000FFFFFFFFULL), |
588 | calc_expire_secs(now, lo->pwr_vec_read_time, | |
589 | B43_LO_PWRVEC_EXPIRE)); | |
590 | ||
591 | fappend("\nCalibrated settings:\n"); | |
592 | list_for_each_entry(cal, &lo->calib_list, list) { | |
593 | bool active; | |
594 | ||
595 | active = (b43_compare_bbatt(&cal->bbatt, &phy->bbatt) && | |
596 | b43_compare_rfatt(&cal->rfatt, &phy->rfatt)); | |
597 | fappend("BB(%d), RF(%d,%d) -> I=%d, Q=%d " | |
598 | "(expires in %lu sec)%s\n", | |
599 | cal->bbatt.att, | |
600 | cal->rfatt.att, cal->rfatt.with_padmix, | |
601 | cal->ctl.i, cal->ctl.q, | |
602 | calc_expire_secs(now, cal->calib_time, | |
603 | B43_LO_CALIB_EXPIRE), | |
604 | active ? " ACTIVE" : ""); | |
605 | } | |
606 | ||
e4d6b795 MB |
607 | fappend("\nUsed RF attenuation values: Value(WithPadmix flag)\n"); |
608 | for (i = 0; i < lo->rfatt_list.len; i++) { | |
609 | fappend("%u(%d), ", | |
610 | lo->rfatt_list.list[i].att, | |
611 | lo->rfatt_list.list[i].with_padmix); | |
612 | } | |
613 | fappend("\n"); | |
614 | fappend("\nUsed Baseband attenuation values:\n"); | |
615 | for (i = 0; i < lo->bbatt_list.len; i++) { | |
616 | fappend("%u, ", | |
617 | lo->bbatt_list.list[i].att); | |
618 | } | |
619 | fappend("\n"); | |
620 | ||
621 | out: | |
622 | return err ? err : count; | |
623 | } | |
624 | ||
625 | #undef fappend | |
626 | ||
627 | static int b43_debugfs_open(struct inode *inode, struct file *file) | |
628 | { | |
629 | file->private_data = inode->i_private; | |
630 | return 0; | |
631 | } | |
632 | ||
633 | static ssize_t b43_debugfs_read(struct file *file, char __user *userbuf, | |
634 | size_t count, loff_t *ppos) | |
635 | { | |
636 | struct b43_wldev *dev; | |
637 | struct b43_debugfs_fops *dfops; | |
638 | struct b43_dfs_file *dfile; | |
7223e8d9 | 639 | ssize_t uninitialized_var(ret); |
e4d6b795 | 640 | char *buf; |
f5eda47f | 641 | const size_t bufsize = 1024 * 16; /* 16 kiB buffer */ |
e4d6b795 MB |
642 | const size_t buforder = get_order(bufsize); |
643 | int err = 0; | |
644 | ||
645 | if (!count) | |
646 | return 0; | |
647 | dev = file->private_data; | |
648 | if (!dev) | |
649 | return -ENODEV; | |
650 | ||
651 | mutex_lock(&dev->wl->mutex); | |
652 | if (b43_status(dev) < B43_STAT_INITIALIZED) { | |
653 | err = -ENODEV; | |
654 | goto out_unlock; | |
655 | } | |
656 | ||
657 | dfops = container_of(file->f_op, struct b43_debugfs_fops, fops); | |
658 | if (!dfops->read) { | |
659 | err = -ENOSYS; | |
660 | goto out_unlock; | |
661 | } | |
662 | dfile = fops_to_dfs_file(dev, dfops); | |
663 | ||
664 | if (!dfile->buffer) { | |
665 | buf = (char *)__get_free_pages(GFP_KERNEL, buforder); | |
666 | if (!buf) { | |
667 | err = -ENOMEM; | |
668 | goto out_unlock; | |
669 | } | |
670 | memset(buf, 0, bufsize); | |
671 | if (dfops->take_irqlock) { | |
672 | spin_lock_irq(&dev->wl->irq_lock); | |
673 | ret = dfops->read(dev, buf, bufsize); | |
674 | spin_unlock_irq(&dev->wl->irq_lock); | |
675 | } else | |
676 | ret = dfops->read(dev, buf, bufsize); | |
677 | if (ret <= 0) { | |
678 | free_pages((unsigned long)buf, buforder); | |
679 | err = ret; | |
680 | goto out_unlock; | |
681 | } | |
682 | dfile->data_len = ret; | |
683 | dfile->buffer = buf; | |
684 | } | |
685 | ||
686 | ret = simple_read_from_buffer(userbuf, count, ppos, | |
687 | dfile->buffer, | |
688 | dfile->data_len); | |
689 | if (*ppos >= dfile->data_len) { | |
690 | free_pages((unsigned long)dfile->buffer, buforder); | |
691 | dfile->buffer = NULL; | |
692 | dfile->data_len = 0; | |
693 | } | |
694 | out_unlock: | |
695 | mutex_unlock(&dev->wl->mutex); | |
696 | ||
697 | return err ? err : ret; | |
698 | } | |
699 | ||
700 | static ssize_t b43_debugfs_write(struct file *file, | |
701 | const char __user *userbuf, | |
702 | size_t count, loff_t *ppos) | |
703 | { | |
704 | struct b43_wldev *dev; | |
705 | struct b43_debugfs_fops *dfops; | |
706 | char *buf; | |
707 | int err = 0; | |
708 | ||
709 | if (!count) | |
710 | return 0; | |
711 | if (count > PAGE_SIZE) | |
712 | return -E2BIG; | |
713 | dev = file->private_data; | |
714 | if (!dev) | |
715 | return -ENODEV; | |
716 | ||
717 | mutex_lock(&dev->wl->mutex); | |
718 | if (b43_status(dev) < B43_STAT_INITIALIZED) { | |
719 | err = -ENODEV; | |
720 | goto out_unlock; | |
721 | } | |
722 | ||
723 | dfops = container_of(file->f_op, struct b43_debugfs_fops, fops); | |
724 | if (!dfops->write) { | |
725 | err = -ENOSYS; | |
726 | goto out_unlock; | |
727 | } | |
728 | ||
729 | buf = (char *)get_zeroed_page(GFP_KERNEL); | |
730 | if (!buf) { | |
731 | err = -ENOMEM; | |
732 | goto out_unlock; | |
733 | } | |
734 | if (copy_from_user(buf, userbuf, count)) { | |
735 | err = -EFAULT; | |
736 | goto out_freepage; | |
737 | } | |
738 | if (dfops->take_irqlock) { | |
739 | spin_lock_irq(&dev->wl->irq_lock); | |
740 | err = dfops->write(dev, buf, count); | |
741 | spin_unlock_irq(&dev->wl->irq_lock); | |
742 | } else | |
743 | err = dfops->write(dev, buf, count); | |
744 | if (err) | |
745 | goto out_freepage; | |
746 | ||
747 | out_freepage: | |
748 | free_page((unsigned long)buf); | |
749 | out_unlock: | |
750 | mutex_unlock(&dev->wl->mutex); | |
751 | ||
752 | return err ? err : count; | |
753 | } | |
754 | ||
755 | ||
756 | #define B43_DEBUGFS_FOPS(name, _read, _write, _take_irqlock) \ | |
757 | static struct b43_debugfs_fops fops_##name = { \ | |
758 | .read = _read, \ | |
759 | .write = _write, \ | |
760 | .fops = { \ | |
761 | .open = b43_debugfs_open, \ | |
762 | .read = b43_debugfs_read, \ | |
763 | .write = b43_debugfs_write, \ | |
764 | }, \ | |
765 | .file_struct_offset = offsetof(struct b43_dfsentry, \ | |
766 | file_##name), \ | |
767 | .take_irqlock = _take_irqlock, \ | |
768 | } | |
769 | ||
6bbc321a MB |
770 | B43_DEBUGFS_FOPS(shm16read, shm16read__read_file, shm16read__write_file, 1); |
771 | B43_DEBUGFS_FOPS(shm16write, NULL, shm16write__write_file, 1); | |
772 | B43_DEBUGFS_FOPS(shm32read, shm32read__read_file, shm32read__write_file, 1); | |
773 | B43_DEBUGFS_FOPS(shm32write, NULL, shm32write__write_file, 1); | |
8bd463f4 MB |
774 | B43_DEBUGFS_FOPS(mmio16read, mmio16read__read_file, mmio16read__write_file, 1); |
775 | B43_DEBUGFS_FOPS(mmio16write, NULL, mmio16write__write_file, 1); | |
776 | B43_DEBUGFS_FOPS(mmio32read, mmio32read__read_file, mmio32read__write_file, 1); | |
777 | B43_DEBUGFS_FOPS(mmio32write, NULL, mmio32write__write_file, 1); | |
e4d6b795 MB |
778 | B43_DEBUGFS_FOPS(tsf, tsf_read_file, tsf_write_file, 1); |
779 | B43_DEBUGFS_FOPS(ucode_regs, ucode_regs_read_file, NULL, 1); | |
780 | B43_DEBUGFS_FOPS(shm, shm_read_file, NULL, 1); | |
781 | B43_DEBUGFS_FOPS(txstat, txstat_read_file, NULL, 0); | |
782 | B43_DEBUGFS_FOPS(txpower_g, txpower_g_read_file, txpower_g_write_file, 0); | |
783 | B43_DEBUGFS_FOPS(restart, NULL, restart_write_file, 1); | |
784 | B43_DEBUGFS_FOPS(loctls, loctls_read_file, NULL, 0); | |
785 | ||
786 | ||
787 | int b43_debug(struct b43_wldev *dev, enum b43_dyndbg feature) | |
788 | { | |
789 | return !!(dev->dfsentry && dev->dfsentry->dyn_debug[feature]); | |
790 | } | |
791 | ||
792 | static void b43_remove_dynamic_debug(struct b43_wldev *dev) | |
793 | { | |
794 | struct b43_dfsentry *e = dev->dfsentry; | |
795 | int i; | |
796 | ||
797 | for (i = 0; i < __B43_NR_DYNDBG; i++) | |
798 | debugfs_remove(e->dyn_debug_dentries[i]); | |
799 | } | |
800 | ||
801 | static void b43_add_dynamic_debug(struct b43_wldev *dev) | |
802 | { | |
803 | struct b43_dfsentry *e = dev->dfsentry; | |
804 | struct dentry *d; | |
805 | ||
806 | #define add_dyn_dbg(name, id, initstate) do { \ | |
807 | e->dyn_debug[id] = (initstate); \ | |
808 | d = debugfs_create_bool(name, 0600, e->subdir, \ | |
809 | &(e->dyn_debug[id])); \ | |
810 | if (!IS_ERR(d)) \ | |
811 | e->dyn_debug_dentries[id] = d; \ | |
812 | } while (0) | |
813 | ||
814 | add_dyn_dbg("debug_xmitpower", B43_DBG_XMITPOWER, 0); | |
815 | add_dyn_dbg("debug_dmaoverflow", B43_DBG_DMAOVERFLOW, 0); | |
816 | add_dyn_dbg("debug_dmaverbose", B43_DBG_DMAVERBOSE, 0); | |
817 | add_dyn_dbg("debug_pwork_fast", B43_DBG_PWORK_FAST, 0); | |
818 | add_dyn_dbg("debug_pwork_stop", B43_DBG_PWORK_STOP, 0); | |
f5eda47f | 819 | add_dyn_dbg("debug_lo", B43_DBG_LO, 0); |
e4d6b795 MB |
820 | |
821 | #undef add_dyn_dbg | |
822 | } | |
823 | ||
824 | void b43_debugfs_add_device(struct b43_wldev *dev) | |
825 | { | |
826 | struct b43_dfsentry *e; | |
827 | struct b43_txstatus_log *log; | |
828 | char devdir[16]; | |
829 | ||
830 | B43_WARN_ON(!dev); | |
831 | e = kzalloc(sizeof(*e), GFP_KERNEL); | |
832 | if (!e) { | |
833 | b43err(dev->wl, "debugfs: add device OOM\n"); | |
834 | return; | |
835 | } | |
836 | e->dev = dev; | |
837 | log = &e->txstatlog; | |
838 | log->log = kcalloc(B43_NR_LOGGED_TXSTATUS, | |
839 | sizeof(struct b43_txstatus), GFP_KERNEL); | |
840 | if (!log->log) { | |
841 | b43err(dev->wl, "debugfs: add device txstatus OOM\n"); | |
842 | kfree(e); | |
843 | return; | |
844 | } | |
845 | log->end = -1; | |
846 | spin_lock_init(&log->lock); | |
847 | ||
848 | dev->dfsentry = e; | |
849 | ||
850 | snprintf(devdir, sizeof(devdir), "%s", wiphy_name(dev->wl->hw->wiphy)); | |
851 | e->subdir = debugfs_create_dir(devdir, rootdir); | |
852 | if (!e->subdir || IS_ERR(e->subdir)) { | |
853 | if (e->subdir == ERR_PTR(-ENODEV)) { | |
854 | b43dbg(dev->wl, "DebugFS (CONFIG_DEBUG_FS) not " | |
855 | "enabled in kernel config\n"); | |
856 | } else { | |
857 | b43err(dev->wl, "debugfs: cannot create %s directory\n", | |
858 | devdir); | |
859 | } | |
860 | dev->dfsentry = NULL; | |
861 | kfree(log->log); | |
862 | kfree(e); | |
863 | return; | |
864 | } | |
865 | ||
8bd463f4 MB |
866 | e->mmio16read_next = 0xFFFF; /* invalid address */ |
867 | e->mmio32read_next = 0xFFFF; /* invalid address */ | |
6bbc321a MB |
868 | e->shm16read_routing_next = 0xFFFFFFFF; /* invalid routing */ |
869 | e->shm16read_addr_next = 0xFFFFFFFF; /* invalid address */ | |
870 | e->shm32read_routing_next = 0xFFFFFFFF; /* invalid routing */ | |
871 | e->shm32read_addr_next = 0xFFFFFFFF; /* invalid address */ | |
8bd463f4 | 872 | |
e4d6b795 MB |
873 | #define ADD_FILE(name, mode) \ |
874 | do { \ | |
875 | struct dentry *d; \ | |
876 | d = debugfs_create_file(__stringify(name), \ | |
877 | mode, e->subdir, dev, \ | |
878 | &fops_##name.fops); \ | |
879 | e->file_##name.dentry = NULL; \ | |
880 | if (!IS_ERR(d)) \ | |
881 | e->file_##name.dentry = d; \ | |
882 | } while (0) | |
883 | ||
884 | ||
6bbc321a MB |
885 | ADD_FILE(shm16read, 0600); |
886 | ADD_FILE(shm16write, 0200); | |
887 | ADD_FILE(shm32read, 0600); | |
888 | ADD_FILE(shm32write, 0200); | |
8bd463f4 MB |
889 | ADD_FILE(mmio16read, 0600); |
890 | ADD_FILE(mmio16write, 0200); | |
891 | ADD_FILE(mmio32read, 0600); | |
892 | ADD_FILE(mmio32write, 0200); | |
e4d6b795 MB |
893 | ADD_FILE(tsf, 0600); |
894 | ADD_FILE(ucode_regs, 0400); | |
895 | ADD_FILE(shm, 0400); | |
896 | ADD_FILE(txstat, 0400); | |
897 | ADD_FILE(txpower_g, 0600); | |
898 | ADD_FILE(restart, 0200); | |
899 | ADD_FILE(loctls, 0400); | |
900 | ||
901 | #undef ADD_FILE | |
902 | ||
903 | b43_add_dynamic_debug(dev); | |
904 | } | |
905 | ||
906 | void b43_debugfs_remove_device(struct b43_wldev *dev) | |
907 | { | |
908 | struct b43_dfsentry *e; | |
909 | ||
910 | if (!dev) | |
911 | return; | |
912 | e = dev->dfsentry; | |
913 | if (!e) | |
914 | return; | |
915 | b43_remove_dynamic_debug(dev); | |
916 | ||
6bbc321a MB |
917 | debugfs_remove(e->file_shm16read.dentry); |
918 | debugfs_remove(e->file_shm16write.dentry); | |
919 | debugfs_remove(e->file_shm32read.dentry); | |
920 | debugfs_remove(e->file_shm32write.dentry); | |
8bd463f4 MB |
921 | debugfs_remove(e->file_mmio16read.dentry); |
922 | debugfs_remove(e->file_mmio16write.dentry); | |
923 | debugfs_remove(e->file_mmio32read.dentry); | |
924 | debugfs_remove(e->file_mmio32write.dentry); | |
e4d6b795 MB |
925 | debugfs_remove(e->file_tsf.dentry); |
926 | debugfs_remove(e->file_ucode_regs.dentry); | |
927 | debugfs_remove(e->file_shm.dentry); | |
928 | debugfs_remove(e->file_txstat.dentry); | |
929 | debugfs_remove(e->file_txpower_g.dentry); | |
930 | debugfs_remove(e->file_restart.dentry); | |
931 | debugfs_remove(e->file_loctls.dentry); | |
932 | ||
933 | debugfs_remove(e->subdir); | |
934 | kfree(e->txstatlog.log); | |
935 | kfree(e); | |
936 | } | |
937 | ||
7a193a5d | 938 | /* Called with IRQs disabled. */ |
e4d6b795 MB |
939 | void b43_debugfs_log_txstat(struct b43_wldev *dev, |
940 | const struct b43_txstatus *status) | |
941 | { | |
942 | struct b43_dfsentry *e = dev->dfsentry; | |
943 | struct b43_txstatus_log *log; | |
944 | struct b43_txstatus *cur; | |
945 | int i; | |
946 | ||
947 | if (!e) | |
948 | return; | |
949 | log = &e->txstatlog; | |
7a193a5d | 950 | spin_lock(&log->lock); /* IRQs are already disabled. */ |
e4d6b795 MB |
951 | i = log->end + 1; |
952 | if (i == B43_NR_LOGGED_TXSTATUS) | |
953 | i = 0; | |
954 | log->end = i; | |
955 | cur = &(log->log[i]); | |
956 | memcpy(cur, status, sizeof(*cur)); | |
957 | spin_unlock(&log->lock); | |
958 | } | |
959 | ||
960 | void b43_debugfs_init(void) | |
961 | { | |
962 | rootdir = debugfs_create_dir(KBUILD_MODNAME, NULL); | |
963 | if (IS_ERR(rootdir)) | |
964 | rootdir = NULL; | |
965 | } | |
966 | ||
967 | void b43_debugfs_exit(void) | |
968 | { | |
969 | debugfs_remove(rootdir); | |
970 | } |