]>
Commit | Line | Data |
---|---|---|
fa1c114f JS |
1 | /* |
2 | * Copyright (c) 2007 Bruno Randolf <bruno@thinktube.com> | |
3 | * | |
4 | * This file is free software: you may copy, redistribute and/or modify it | |
5 | * under the terms of the GNU General Public License as published by the | |
6 | * Free Software Foundation, either version 2 of the License, or (at your | |
7 | * option) any later version. | |
8 | * | |
9 | * This file is distributed in the hope that it will be useful, but | |
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | * General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License | |
15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | * | |
17 | * | |
18 | * This file incorporates work covered by the following copyright and | |
19 | * permission notice: | |
20 | * | |
21 | * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting | |
22 | * Copyright (c) 2004-2005 Atheros Communications, Inc. | |
23 | * Copyright (c) 2006 Devicescape Software, Inc. | |
24 | * Copyright (c) 2007 Jiri Slaby <jirislaby@gmail.com> | |
25 | * Copyright (c) 2007 Luis R. Rodriguez <mcgrof@winlab.rutgers.edu> | |
26 | * | |
27 | * All rights reserved. | |
28 | * | |
29 | * Redistribution and use in source and binary forms, with or without | |
30 | * modification, are permitted provided that the following conditions | |
31 | * are met: | |
32 | * 1. Redistributions of source code must retain the above copyright | |
33 | * notice, this list of conditions and the following disclaimer, | |
34 | * without modification. | |
35 | * 2. Redistributions in binary form must reproduce at minimum a disclaimer | |
36 | * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any | |
37 | * redistribution must be conditioned upon including a substantially | |
38 | * similar Disclaimer requirement for further binary redistribution. | |
39 | * 3. Neither the names of the above-listed copyright holders nor the names | |
40 | * of any contributors may be used to endorse or promote products derived | |
41 | * from this software without specific prior written permission. | |
42 | * | |
43 | * Alternatively, this software may be distributed under the terms of the | |
44 | * GNU General Public License ("GPL") version 2 as published by the Free | |
45 | * Software Foundation. | |
46 | * | |
47 | * NO WARRANTY | |
48 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
49 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
50 | * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY | |
51 | * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL | |
52 | * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, | |
53 | * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
54 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
55 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER | |
56 | * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
57 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | |
58 | * THE POSSIBILITY OF SUCH DAMAGES. | |
59 | */ | |
60 | ||
61 | #include "debug.h" | |
62 | #include "base.h" | |
63 | ||
64 | static unsigned int ath5k_debug; | |
65 | module_param_named(debug, ath5k_debug, uint, 0); | |
66 | ||
67 | ||
68 | #if ATH5K_DEBUG | |
69 | ||
70 | #include <linux/seq_file.h> | |
71 | #include "reg.h" | |
72 | ||
73 | static struct dentry *ath5k_global_debugfs; | |
74 | ||
75 | static int ath5k_debugfs_open(struct inode *inode, struct file *file) | |
76 | { | |
77 | file->private_data = inode->i_private; | |
78 | return 0; | |
79 | } | |
80 | ||
81 | ||
82 | /* debugfs: registers */ | |
83 | ||
84 | struct reg { | |
85 | char *name; | |
86 | int addr; | |
87 | }; | |
88 | ||
89 | #define REG_STRUCT_INIT(r) { #r, r } | |
90 | ||
91 | /* just a few random registers, might want to add more */ | |
92 | static struct reg regs[] = { | |
93 | REG_STRUCT_INIT(AR5K_CR), | |
94 | REG_STRUCT_INIT(AR5K_RXDP), | |
95 | REG_STRUCT_INIT(AR5K_CFG), | |
96 | REG_STRUCT_INIT(AR5K_IER), | |
97 | REG_STRUCT_INIT(AR5K_BCR), | |
98 | REG_STRUCT_INIT(AR5K_RTSD0), | |
99 | REG_STRUCT_INIT(AR5K_RTSD1), | |
100 | REG_STRUCT_INIT(AR5K_TXCFG), | |
101 | REG_STRUCT_INIT(AR5K_RXCFG), | |
102 | REG_STRUCT_INIT(AR5K_RXJLA), | |
103 | REG_STRUCT_INIT(AR5K_MIBC), | |
104 | REG_STRUCT_INIT(AR5K_TOPS), | |
105 | REG_STRUCT_INIT(AR5K_RXNOFRM), | |
106 | REG_STRUCT_INIT(AR5K_TXNOFRM), | |
107 | REG_STRUCT_INIT(AR5K_RPGTO), | |
108 | REG_STRUCT_INIT(AR5K_RFCNT), | |
109 | REG_STRUCT_INIT(AR5K_MISC), | |
110 | REG_STRUCT_INIT(AR5K_QCUDCU_CLKGT), | |
111 | REG_STRUCT_INIT(AR5K_ISR), | |
112 | REG_STRUCT_INIT(AR5K_PISR), | |
113 | REG_STRUCT_INIT(AR5K_SISR0), | |
114 | REG_STRUCT_INIT(AR5K_SISR1), | |
115 | REG_STRUCT_INIT(AR5K_SISR2), | |
116 | REG_STRUCT_INIT(AR5K_SISR3), | |
117 | REG_STRUCT_INIT(AR5K_SISR4), | |
118 | REG_STRUCT_INIT(AR5K_IMR), | |
119 | REG_STRUCT_INIT(AR5K_PIMR), | |
120 | REG_STRUCT_INIT(AR5K_SIMR0), | |
121 | REG_STRUCT_INIT(AR5K_SIMR1), | |
122 | REG_STRUCT_INIT(AR5K_SIMR2), | |
123 | REG_STRUCT_INIT(AR5K_SIMR3), | |
124 | REG_STRUCT_INIT(AR5K_SIMR4), | |
125 | REG_STRUCT_INIT(AR5K_DCM_ADDR), | |
126 | REG_STRUCT_INIT(AR5K_DCCFG), | |
127 | REG_STRUCT_INIT(AR5K_CCFG), | |
128 | REG_STRUCT_INIT(AR5K_CPC0), | |
129 | REG_STRUCT_INIT(AR5K_CPC1), | |
130 | REG_STRUCT_INIT(AR5K_CPC2), | |
131 | REG_STRUCT_INIT(AR5K_CPC3), | |
132 | REG_STRUCT_INIT(AR5K_CPCORN), | |
133 | REG_STRUCT_INIT(AR5K_RESET_CTL), | |
134 | REG_STRUCT_INIT(AR5K_SLEEP_CTL), | |
135 | REG_STRUCT_INIT(AR5K_INTPEND), | |
136 | REG_STRUCT_INIT(AR5K_SFR), | |
137 | REG_STRUCT_INIT(AR5K_PCICFG), | |
138 | REG_STRUCT_INIT(AR5K_GPIOCR), | |
139 | REG_STRUCT_INIT(AR5K_GPIODO), | |
140 | REG_STRUCT_INIT(AR5K_SREV), | |
141 | }; | |
142 | ||
143 | static void *reg_start(struct seq_file *seq, loff_t *pos) | |
144 | { | |
145 | return *pos < ARRAY_SIZE(regs) ? ®s[*pos] : NULL; | |
146 | } | |
147 | ||
148 | static void reg_stop(struct seq_file *seq, void *p) | |
149 | { | |
150 | /* nothing to do */ | |
151 | } | |
152 | ||
153 | static void *reg_next(struct seq_file *seq, void *p, loff_t *pos) | |
154 | { | |
155 | ++*pos; | |
156 | return *pos < ARRAY_SIZE(regs) ? ®s[*pos] : NULL; | |
157 | } | |
158 | ||
159 | static int reg_show(struct seq_file *seq, void *p) | |
160 | { | |
161 | struct ath5k_softc *sc = seq->private; | |
162 | struct reg *r = p; | |
163 | seq_printf(seq, "%-25s0x%08x\n", r->name, | |
164 | ath5k_hw_reg_read(sc->ah, r->addr)); | |
165 | return 0; | |
166 | } | |
167 | ||
168 | static struct seq_operations register_seq_ops = { | |
169 | .start = reg_start, | |
170 | .next = reg_next, | |
171 | .stop = reg_stop, | |
172 | .show = reg_show | |
173 | }; | |
174 | ||
175 | static int open_file_registers(struct inode *inode, struct file *file) | |
176 | { | |
177 | struct seq_file *s; | |
178 | int res; | |
179 | res = seq_open(file, ®ister_seq_ops); | |
180 | if (res == 0) { | |
181 | s = file->private_data; | |
182 | s->private = inode->i_private; | |
183 | } | |
184 | return res; | |
185 | } | |
186 | ||
187 | static const struct file_operations fops_registers = { | |
188 | .open = open_file_registers, | |
189 | .read = seq_read, | |
190 | .llseek = seq_lseek, | |
191 | .release = seq_release, | |
192 | .owner = THIS_MODULE, | |
193 | }; | |
194 | ||
195 | ||
196 | /* debugfs: TSF */ | |
197 | ||
198 | static ssize_t read_file_tsf(struct file *file, char __user *user_buf, | |
199 | size_t count, loff_t *ppos) | |
200 | { | |
201 | struct ath5k_softc *sc = file->private_data; | |
202 | char buf[100]; | |
203 | snprintf(buf, 100, "0x%016llx\n", ath5k_hw_get_tsf64(sc->ah)); | |
204 | return simple_read_from_buffer(user_buf, count, ppos, buf, 19); | |
205 | } | |
206 | ||
207 | static ssize_t write_file_tsf(struct file *file, | |
208 | const char __user *userbuf, | |
209 | size_t count, loff_t *ppos) | |
210 | { | |
211 | struct ath5k_softc *sc = file->private_data; | |
212 | if (strncmp(userbuf, "reset", 5) == 0) { | |
213 | ath5k_hw_reset_tsf(sc->ah); | |
214 | printk(KERN_INFO "debugfs reset TSF\n"); | |
215 | } | |
216 | return count; | |
217 | } | |
218 | ||
219 | static const struct file_operations fops_tsf = { | |
220 | .read = read_file_tsf, | |
221 | .write = write_file_tsf, | |
222 | .open = ath5k_debugfs_open, | |
223 | .owner = THIS_MODULE, | |
224 | }; | |
225 | ||
226 | ||
227 | /* debugfs: beacons */ | |
228 | ||
229 | static ssize_t read_file_beacon(struct file *file, char __user *user_buf, | |
230 | size_t count, loff_t *ppos) | |
231 | { | |
232 | struct ath5k_softc *sc = file->private_data; | |
233 | struct ath5k_hw *ah = sc->ah; | |
234 | char buf[1000]; | |
235 | int len = 0; | |
236 | unsigned int v; | |
237 | u64 tsf; | |
238 | ||
239 | v = ath5k_hw_reg_read(sc->ah, AR5K_BEACON); | |
240 | len += snprintf(buf+len, sizeof(buf)-len, | |
241 | "%-24s0x%08x\tintval: %d\tTIM: 0x%x\n", | |
242 | "AR5K_BEACON", v, v & AR5K_BEACON_PERIOD, | |
243 | (v & AR5K_BEACON_TIM) >> AR5K_BEACON_TIM_S); | |
244 | ||
245 | len += snprintf(buf+len, sizeof(buf)-len, "%-24s0x%08x\n", | |
246 | "AR5K_LAST_TSTP", ath5k_hw_reg_read(sc->ah, AR5K_LAST_TSTP)); | |
247 | ||
248 | len += snprintf(buf+len, sizeof(buf)-len, "%-24s0x%08x\n\n", | |
249 | "AR5K_BEACON_CNT", ath5k_hw_reg_read(sc->ah, AR5K_BEACON_CNT)); | |
250 | ||
251 | v = ath5k_hw_reg_read(sc->ah, AR5K_TIMER0); | |
252 | len += snprintf(buf+len, sizeof(buf)-len, "%-24s0x%08x\tTU: %08x\n", | |
253 | "AR5K_TIMER0 (TBTT)", v, v); | |
254 | ||
255 | v = ath5k_hw_reg_read(sc->ah, AR5K_TIMER1); | |
256 | len += snprintf(buf+len, sizeof(buf)-len, "%-24s0x%08x\tTU: %08x\n", | |
257 | "AR5K_TIMER1 (DMA)", v, v >> 3); | |
258 | ||
259 | v = ath5k_hw_reg_read(sc->ah, AR5K_TIMER2); | |
260 | len += snprintf(buf+len, sizeof(buf)-len, "%-24s0x%08x\tTU: %08x\n", | |
261 | "AR5K_TIMER2 (SWBA)", v, v >> 3); | |
262 | ||
263 | v = ath5k_hw_reg_read(sc->ah, AR5K_TIMER3); | |
264 | len += snprintf(buf+len, sizeof(buf)-len, "%-24s0x%08x\tTU: %08x\n", | |
265 | "AR5K_TIMER3 (ATIM)", v, v); | |
266 | ||
267 | tsf = ath5k_hw_get_tsf64(sc->ah); | |
268 | len += snprintf(buf+len, sizeof(buf)-len, | |
269 | "TSF\t\t0x%016llx\tTU: %08x\n", tsf, TSF_TO_TU(tsf)); | |
270 | ||
271 | return simple_read_from_buffer(user_buf, count, ppos, buf, len); | |
272 | } | |
273 | ||
274 | static ssize_t write_file_beacon(struct file *file, | |
275 | const char __user *userbuf, | |
276 | size_t count, loff_t *ppos) | |
277 | { | |
278 | struct ath5k_softc *sc = file->private_data; | |
279 | struct ath5k_hw *ah = sc->ah; | |
280 | ||
281 | if (strncmp(userbuf, "disable", 7) == 0) { | |
282 | AR5K_REG_DISABLE_BITS(ah, AR5K_BEACON, AR5K_BEACON_ENABLE); | |
283 | printk(KERN_INFO "debugfs disable beacons\n"); | |
284 | } else if (strncmp(userbuf, "enable", 6) == 0) { | |
285 | AR5K_REG_ENABLE_BITS(ah, AR5K_BEACON, AR5K_BEACON_ENABLE); | |
286 | printk(KERN_INFO "debugfs enable beacons\n"); | |
287 | } | |
288 | return count; | |
289 | } | |
290 | ||
291 | static const struct file_operations fops_beacon = { | |
292 | .read = read_file_beacon, | |
293 | .write = write_file_beacon, | |
294 | .open = ath5k_debugfs_open, | |
295 | .owner = THIS_MODULE, | |
296 | }; | |
297 | ||
298 | ||
299 | /* debugfs: reset */ | |
300 | ||
301 | static ssize_t write_file_reset(struct file *file, | |
302 | const char __user *userbuf, | |
303 | size_t count, loff_t *ppos) | |
304 | { | |
305 | struct ath5k_softc *sc = file->private_data; | |
306 | tasklet_schedule(&sc->restq); | |
307 | return count; | |
308 | } | |
309 | ||
310 | static const struct file_operations fops_reset = { | |
311 | .write = write_file_reset, | |
312 | .open = ath5k_debugfs_open, | |
313 | .owner = THIS_MODULE, | |
314 | }; | |
315 | ||
316 | ||
317 | /* init */ | |
318 | ||
319 | void | |
320 | ath5k_debug_init(void) | |
321 | { | |
322 | ath5k_global_debugfs = debugfs_create_dir("ath5k", NULL); | |
323 | } | |
324 | ||
325 | void | |
326 | ath5k_debug_init_device(struct ath5k_softc *sc) | |
327 | { | |
328 | sc->debug.level = ath5k_debug; | |
329 | sc->debug.debugfs_phydir = debugfs_create_dir(wiphy_name(sc->hw->wiphy), | |
330 | ath5k_global_debugfs); | |
331 | sc->debug.debugfs_debug = debugfs_create_u32("debug", | |
332 | 0666, sc->debug.debugfs_phydir, &sc->debug.level); | |
333 | ||
334 | sc->debug.debugfs_registers = debugfs_create_file("registers", 0444, | |
335 | sc->debug.debugfs_phydir, | |
336 | sc, &fops_registers); | |
337 | ||
338 | sc->debug.debugfs_tsf = debugfs_create_file("tsf", 0666, | |
339 | sc->debug.debugfs_phydir, | |
340 | sc, &fops_tsf); | |
341 | ||
342 | sc->debug.debugfs_beacon = debugfs_create_file("beacon", 0666, | |
343 | sc->debug.debugfs_phydir, | |
344 | sc, &fops_beacon); | |
345 | ||
346 | sc->debug.debugfs_reset = debugfs_create_file("reset", 0222, | |
347 | sc->debug.debugfs_phydir, | |
348 | sc, &fops_reset); | |
349 | } | |
350 | ||
351 | void | |
352 | ath5k_debug_finish(void) | |
353 | { | |
354 | debugfs_remove(ath5k_global_debugfs); | |
355 | } | |
356 | ||
357 | void | |
358 | ath5k_debug_finish_device(struct ath5k_softc *sc) | |
359 | { | |
360 | debugfs_remove(sc->debug.debugfs_debug); | |
361 | debugfs_remove(sc->debug.debugfs_registers); | |
362 | debugfs_remove(sc->debug.debugfs_tsf); | |
363 | debugfs_remove(sc->debug.debugfs_beacon); | |
364 | debugfs_remove(sc->debug.debugfs_reset); | |
365 | debugfs_remove(sc->debug.debugfs_phydir); | |
366 | } | |
367 | ||
368 | ||
369 | /* functions used in other places */ | |
370 | ||
371 | void | |
372 | ath5k_debug_dump_modes(struct ath5k_softc *sc, struct ieee80211_hw_mode *modes) | |
373 | { | |
374 | unsigned int m, i; | |
375 | ||
376 | if (likely(!(sc->debug.level & ATH5K_DEBUG_DUMPMODES))) | |
377 | return; | |
378 | ||
379 | for (m = 0; m < NUM_DRIVER_MODES; m++) { | |
380 | printk(KERN_DEBUG "Mode %u: channels %d, rates %d\n", m, | |
381 | modes[m].num_channels, modes[m].num_rates); | |
382 | printk(KERN_DEBUG " channels:\n"); | |
383 | for (i = 0; i < modes[m].num_channels; i++) | |
384 | printk(KERN_DEBUG " %3d %d %.4x %.4x\n", | |
385 | modes[m].channels[i].chan, | |
386 | modes[m].channels[i].freq, | |
387 | modes[m].channels[i].val, | |
388 | modes[m].channels[i].flag); | |
389 | printk(KERN_DEBUG " rates:\n"); | |
390 | for (i = 0; i < modes[m].num_rates; i++) | |
391 | printk(KERN_DEBUG " %4d %.4x %.4x %.4x\n", | |
392 | modes[m].rates[i].rate, | |
393 | modes[m].rates[i].val, | |
394 | modes[m].rates[i].flags, | |
395 | modes[m].rates[i].val2); | |
396 | } | |
397 | } | |
398 | ||
399 | static inline void | |
400 | ath5k_debug_printrxbuf(struct ath5k_buf *bf, int done) | |
401 | { | |
402 | struct ath5k_desc *ds = bf->desc; | |
403 | ||
404 | printk(KERN_DEBUG "R (%p %llx) %08x %08x %08x %08x %08x %08x %c\n", | |
405 | ds, (unsigned long long)bf->daddr, | |
406 | ds->ds_link, ds->ds_data, ds->ds_ctl0, ds->ds_ctl1, | |
407 | ds->ds_hw[0], ds->ds_hw[1], | |
408 | !done ? ' ' : (ds->ds_rxstat.rs_status == 0) ? '*' : '!'); | |
409 | } | |
410 | ||
411 | void | |
412 | ath5k_debug_printrxbuffs(struct ath5k_softc *sc, struct ath5k_hw *ah) | |
413 | { | |
414 | struct ath5k_desc *ds; | |
415 | struct ath5k_buf *bf; | |
416 | int status; | |
417 | ||
418 | if (likely(!(sc->debug.level & | |
419 | (ATH5K_DEBUG_RESET | ATH5K_DEBUG_FATAL)))) | |
420 | return; | |
421 | ||
422 | printk(KERN_DEBUG "rx queue %x, link %p\n", | |
423 | ath5k_hw_get_rx_buf(ah), sc->rxlink); | |
424 | ||
425 | spin_lock_bh(&sc->rxbuflock); | |
426 | list_for_each_entry(bf, &sc->rxbuf, list) { | |
427 | ds = bf->desc; | |
428 | status = ah->ah_proc_rx_desc(ah, ds); | |
429 | if (!status || (sc->debug.level & ATH5K_DEBUG_FATAL)) | |
430 | ath5k_debug_printrxbuf(bf, status == 0); | |
431 | } | |
432 | spin_unlock_bh(&sc->rxbuflock); | |
433 | } | |
434 | ||
435 | void | |
436 | ath5k_debug_dump_skb(struct ath5k_softc *sc, | |
437 | struct sk_buff *skb, const char *prefix, int tx) | |
438 | { | |
439 | char buf[16]; | |
440 | ||
441 | if (likely(!((tx && (sc->debug.level & ATH5K_DEBUG_DUMP_TX)) || | |
442 | (!tx && (sc->debug.level & ATH5K_DEBUG_DUMP_RX))))) | |
443 | return; | |
444 | ||
445 | snprintf(buf, sizeof(buf), "%s %s", wiphy_name(sc->hw->wiphy), prefix); | |
446 | ||
447 | print_hex_dump_bytes(buf, DUMP_PREFIX_NONE, skb->data, | |
448 | min(200U, skb->len)); | |
449 | ||
450 | printk(KERN_DEBUG "\n"); | |
451 | } | |
452 | ||
453 | void | |
454 | ath5k_debug_printtxbuf(struct ath5k_softc *sc, | |
455 | struct ath5k_buf *bf, int done) | |
456 | { | |
457 | struct ath5k_desc *ds = bf->desc; | |
458 | ||
459 | if (likely(!(sc->debug.level & ATH5K_DEBUG_RESET))) | |
460 | return; | |
461 | ||
462 | printk(KERN_DEBUG "T (%p %llx) %08x %08x %08x %08x %08x %08x %08x " | |
463 | "%08x %c\n", ds, (unsigned long long)bf->daddr, ds->ds_link, | |
464 | ds->ds_data, ds->ds_ctl0, ds->ds_ctl1, | |
465 | ds->ds_hw[0], ds->ds_hw[1], ds->ds_hw[2], ds->ds_hw[3], | |
466 | !done ? ' ' : (ds->ds_txstat.ts_status == 0) ? '*' : '!'); | |
467 | } | |
468 | ||
469 | #endif /* if ATH5K_DEBUG */ |