| /* |
| * Copyright (c) 2007-2008 Bruno Randolf <bruno@thinktube.com> |
| * |
| * This file is free software: you may copy, redistribute and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation, either version 2 of the License, or (at your |
| * option) any later version. |
| * |
| * This file is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * |
| * |
| * This file incorporates work covered by the following copyright and |
| * permission notice: |
| * |
| * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting |
| * Copyright (c) 2004-2005 Atheros Communications, Inc. |
| * Copyright (c) 2006 Devicescape Software, Inc. |
| * Copyright (c) 2007 Jiri Slaby <jirislaby@gmail.com> |
| * Copyright (c) 2007 Luis R. Rodriguez <mcgrof@winlab.rutgers.edu> |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer, |
| * without modification. |
| * 2. Redistributions in binary form must reproduce at minimum a disclaimer |
| * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any |
| * redistribution must be conditioned upon including a substantially |
| * similar Disclaimer requirement for further binary redistribution. |
| * 3. Neither the names of the above-listed copyright holders nor the names |
| * of any contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * Alternatively, this software may be distributed under the terms of the |
| * GNU General Public License ("GPL") version 2 as published by the Free |
| * Software Foundation. |
| * |
| * NO WARRANTY |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY |
| * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL |
| * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, |
| * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER |
| * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGES. |
| */ |
| |
| #include "debug.h" |
| #include "base.h" |
| |
| static unsigned int ath5k_debug; |
| module_param_named(debug, ath5k_debug, uint, 0); |
| |
| |
| #if ATH5K_DEBUG |
| |
| #include <linux/seq_file.h> |
| #include "reg.h" |
| |
| static struct dentry *ath5k_global_debugfs; |
| |
| static int ath5k_debugfs_open(struct inode *inode, struct file *file) |
| { |
| file->private_data = inode->i_private; |
| return 0; |
| } |
| |
| |
| /* debugfs: registers */ |
| |
| struct reg { |
| char *name; |
| int addr; |
| }; |
| |
| #define REG_STRUCT_INIT(r) { #r, r } |
| |
| /* just a few random registers, might want to add more */ |
| static struct reg regs[] = { |
| REG_STRUCT_INIT(AR5K_CR), |
| REG_STRUCT_INIT(AR5K_RXDP), |
| REG_STRUCT_INIT(AR5K_CFG), |
| REG_STRUCT_INIT(AR5K_IER), |
| REG_STRUCT_INIT(AR5K_BCR), |
| REG_STRUCT_INIT(AR5K_RTSD0), |
| REG_STRUCT_INIT(AR5K_RTSD1), |
| REG_STRUCT_INIT(AR5K_TXCFG), |
| REG_STRUCT_INIT(AR5K_RXCFG), |
| REG_STRUCT_INIT(AR5K_RXJLA), |
| REG_STRUCT_INIT(AR5K_MIBC), |
| REG_STRUCT_INIT(AR5K_TOPS), |
| REG_STRUCT_INIT(AR5K_RXNOFRM), |
| REG_STRUCT_INIT(AR5K_TXNOFRM), |
| REG_STRUCT_INIT(AR5K_RPGTO), |
| REG_STRUCT_INIT(AR5K_RFCNT), |
| REG_STRUCT_INIT(AR5K_MISC), |
| REG_STRUCT_INIT(AR5K_QCUDCU_CLKGT), |
| REG_STRUCT_INIT(AR5K_ISR), |
| REG_STRUCT_INIT(AR5K_PISR), |
| REG_STRUCT_INIT(AR5K_SISR0), |
| REG_STRUCT_INIT(AR5K_SISR1), |
| REG_STRUCT_INIT(AR5K_SISR2), |
| REG_STRUCT_INIT(AR5K_SISR3), |
| REG_STRUCT_INIT(AR5K_SISR4), |
| REG_STRUCT_INIT(AR5K_IMR), |
| REG_STRUCT_INIT(AR5K_PIMR), |
| REG_STRUCT_INIT(AR5K_SIMR0), |
| REG_STRUCT_INIT(AR5K_SIMR1), |
| REG_STRUCT_INIT(AR5K_SIMR2), |
| REG_STRUCT_INIT(AR5K_SIMR3), |
| REG_STRUCT_INIT(AR5K_SIMR4), |
| REG_STRUCT_INIT(AR5K_DCM_ADDR), |
| REG_STRUCT_INIT(AR5K_DCCFG), |
| REG_STRUCT_INIT(AR5K_CCFG), |
| REG_STRUCT_INIT(AR5K_CPC0), |
| REG_STRUCT_INIT(AR5K_CPC1), |
| REG_STRUCT_INIT(AR5K_CPC2), |
| REG_STRUCT_INIT(AR5K_CPC3), |
| REG_STRUCT_INIT(AR5K_CPCORN), |
| REG_STRUCT_INIT(AR5K_RESET_CTL), |
| REG_STRUCT_INIT(AR5K_SLEEP_CTL), |
| REG_STRUCT_INIT(AR5K_INTPEND), |
| REG_STRUCT_INIT(AR5K_SFR), |
| REG_STRUCT_INIT(AR5K_PCICFG), |
| REG_STRUCT_INIT(AR5K_GPIOCR), |
| REG_STRUCT_INIT(AR5K_GPIODO), |
| REG_STRUCT_INIT(AR5K_SREV), |
| }; |
| |
| static void *reg_start(struct seq_file *seq, loff_t *pos) |
| { |
| return *pos < ARRAY_SIZE(regs) ? ®s[*pos] : NULL; |
| } |
| |
| static void reg_stop(struct seq_file *seq, void *p) |
| { |
| /* nothing to do */ |
| } |
| |
| static void *reg_next(struct seq_file *seq, void *p, loff_t *pos) |
| { |
| ++*pos; |
| return *pos < ARRAY_SIZE(regs) ? ®s[*pos] : NULL; |
| } |
| |
| static int reg_show(struct seq_file *seq, void *p) |
| { |
| struct ath5k_softc *sc = seq->private; |
| struct reg *r = p; |
| seq_printf(seq, "%-25s0x%08x\n", r->name, |
| ath5k_hw_reg_read(sc->ah, r->addr)); |
| return 0; |
| } |
| |
| static struct seq_operations register_seq_ops = { |
| .start = reg_start, |
| .next = reg_next, |
| .stop = reg_stop, |
| .show = reg_show |
| }; |
| |
| static int open_file_registers(struct inode *inode, struct file *file) |
| { |
| struct seq_file *s; |
| int res; |
| res = seq_open(file, ®ister_seq_ops); |
| if (res == 0) { |
| s = file->private_data; |
| s->private = inode->i_private; |
| } |
| return res; |
| } |
| |
| static const struct file_operations fops_registers = { |
| .open = open_file_registers, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = seq_release, |
| .owner = THIS_MODULE, |
| }; |
| |
| |
| /* debugfs: TSF */ |
| |
| static ssize_t read_file_tsf(struct file *file, char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| struct ath5k_softc *sc = file->private_data; |
| char buf[100]; |
| snprintf(buf, sizeof(buf), "0x%016llx\n", ath5k_hw_get_tsf64(sc->ah)); |
| return simple_read_from_buffer(user_buf, count, ppos, buf, 19); |
| } |
| |
| static ssize_t write_file_tsf(struct file *file, |
| const char __user *userbuf, |
| size_t count, loff_t *ppos) |
| { |
| struct ath5k_softc *sc = file->private_data; |
| char buf[20]; |
| |
| if (copy_from_user(buf, userbuf, min(count, sizeof(buf)))) |
| return -EFAULT; |
| |
| if (strncmp(buf, "reset", 5) == 0) { |
| ath5k_hw_reset_tsf(sc->ah); |
| printk(KERN_INFO "debugfs reset TSF\n"); |
| } |
| return count; |
| } |
| |
| static const struct file_operations fops_tsf = { |
| .read = read_file_tsf, |
| .write = write_file_tsf, |
| .open = ath5k_debugfs_open, |
| .owner = THIS_MODULE, |
| }; |
| |
| |
| /* debugfs: beacons */ |
| |
| static ssize_t read_file_beacon(struct file *file, char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| struct ath5k_softc *sc = file->private_data; |
| struct ath5k_hw *ah = sc->ah; |
| char buf[500]; |
| unsigned int len = 0; |
| unsigned int v; |
| u64 tsf; |
| |
| v = ath5k_hw_reg_read(sc->ah, AR5K_BEACON); |
| len += snprintf(buf+len, sizeof(buf)-len, |
| "%-24s0x%08x\tintval: %d\tTIM: 0x%x\n", |
| "AR5K_BEACON", v, v & AR5K_BEACON_PERIOD, |
| (v & AR5K_BEACON_TIM) >> AR5K_BEACON_TIM_S); |
| |
| len += snprintf(buf+len, sizeof(buf)-len, "%-24s0x%08x\n", |
| "AR5K_LAST_TSTP", ath5k_hw_reg_read(sc->ah, AR5K_LAST_TSTP)); |
| |
| len += snprintf(buf+len, sizeof(buf)-len, "%-24s0x%08x\n\n", |
| "AR5K_BEACON_CNT", ath5k_hw_reg_read(sc->ah, AR5K_BEACON_CNT)); |
| |
| v = ath5k_hw_reg_read(sc->ah, AR5K_TIMER0); |
| len += snprintf(buf+len, sizeof(buf)-len, "%-24s0x%08x\tTU: %08x\n", |
| "AR5K_TIMER0 (TBTT)", v, v); |
| |
| v = ath5k_hw_reg_read(sc->ah, AR5K_TIMER1); |
| len += snprintf(buf+len, sizeof(buf)-len, "%-24s0x%08x\tTU: %08x\n", |
| "AR5K_TIMER1 (DMA)", v, v >> 3); |
| |
| v = ath5k_hw_reg_read(sc->ah, AR5K_TIMER2); |
| len += snprintf(buf+len, sizeof(buf)-len, "%-24s0x%08x\tTU: %08x\n", |
| "AR5K_TIMER2 (SWBA)", v, v >> 3); |
| |
| v = ath5k_hw_reg_read(sc->ah, AR5K_TIMER3); |
| len += snprintf(buf+len, sizeof(buf)-len, "%-24s0x%08x\tTU: %08x\n", |
| "AR5K_TIMER3 (ATIM)", v, v); |
| |
| tsf = ath5k_hw_get_tsf64(sc->ah); |
| len += snprintf(buf+len, sizeof(buf)-len, |
| "TSF\t\t0x%016llx\tTU: %08x\n", tsf, TSF_TO_TU(tsf)); |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buf, len); |
| } |
| |
| static ssize_t write_file_beacon(struct file *file, |
| const char __user *userbuf, |
| size_t count, loff_t *ppos) |
| { |
| struct ath5k_softc *sc = file->private_data; |
| struct ath5k_hw *ah = sc->ah; |
| char buf[20]; |
| |
| if (copy_from_user(buf, userbuf, min(count, sizeof(buf)))) |
| return -EFAULT; |
| |
| if (strncmp(buf, "disable", 7) == 0) { |
| AR5K_REG_DISABLE_BITS(ah, AR5K_BEACON, AR5K_BEACON_ENABLE); |
| printk(KERN_INFO "debugfs disable beacons\n"); |
| } else if (strncmp(buf, "enable", 6) == 0) { |
| AR5K_REG_ENABLE_BITS(ah, AR5K_BEACON, AR5K_BEACON_ENABLE); |
| printk(KERN_INFO "debugfs enable beacons\n"); |
| } |
| return count; |
| } |
| |
| static const struct file_operations fops_beacon = { |
| .read = read_file_beacon, |
| .write = write_file_beacon, |
| .open = ath5k_debugfs_open, |
| .owner = THIS_MODULE, |
| }; |
| |
| |
| /* debugfs: reset */ |
| |
| static ssize_t write_file_reset(struct file *file, |
| const char __user *userbuf, |
| size_t count, loff_t *ppos) |
| { |
| struct ath5k_softc *sc = file->private_data; |
| tasklet_schedule(&sc->restq); |
| return count; |
| } |
| |
| static const struct file_operations fops_reset = { |
| .write = write_file_reset, |
| .open = ath5k_debugfs_open, |
| .owner = THIS_MODULE, |
| }; |
| |
| |
| /* debugfs: debug level */ |
| |
| static struct { |
| enum ath5k_debug_level level; |
| const char *name; |
| const char *desc; |
| } dbg_info[] = { |
| { ATH5K_DEBUG_RESET, "reset", "reset and initialization" }, |
| { ATH5K_DEBUG_INTR, "intr", "interrupt handling" }, |
| { ATH5K_DEBUG_MODE, "mode", "mode init/setup" }, |
| { ATH5K_DEBUG_XMIT, "xmit", "basic xmit operation" }, |
| { ATH5K_DEBUG_BEACON, "beacon", "beacon handling" }, |
| { ATH5K_DEBUG_CALIBRATE, "calib", "periodic calibration" }, |
| { ATH5K_DEBUG_TXPOWER, "txpower", "transmit power setting" }, |
| { ATH5K_DEBUG_LED, "led", "LED mamagement" }, |
| { ATH5K_DEBUG_DUMP_RX, "dumprx", "print received skb content" }, |
| { ATH5K_DEBUG_DUMP_TX, "dumptx", "print transmit skb content" }, |
| { ATH5K_DEBUG_DUMPMODES, "dumpmodes", "dump modes" }, |
| { ATH5K_DEBUG_TRACE, "trace", "trace function calls" }, |
| { ATH5K_DEBUG_ANY, "all", "show all debug levels" }, |
| }; |
| |
| static ssize_t read_file_debug(struct file *file, char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| struct ath5k_softc *sc = file->private_data; |
| char buf[700]; |
| unsigned int len = 0; |
| unsigned int i; |
| |
| len += snprintf(buf+len, sizeof(buf)-len, |
| "DEBUG LEVEL: 0x%08x\n\n", sc->debug.level); |
| |
| for (i = 0; i < ARRAY_SIZE(dbg_info) - 1; i++) { |
| len += snprintf(buf+len, sizeof(buf)-len, |
| "%10s %c 0x%08x - %s\n", dbg_info[i].name, |
| sc->debug.level & dbg_info[i].level ? '+' : ' ', |
| dbg_info[i].level, dbg_info[i].desc); |
| } |
| len += snprintf(buf+len, sizeof(buf)-len, |
| "%10s %c 0x%08x - %s\n", dbg_info[i].name, |
| sc->debug.level == dbg_info[i].level ? '+' : ' ', |
| dbg_info[i].level, dbg_info[i].desc); |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buf, len); |
| } |
| |
| static ssize_t write_file_debug(struct file *file, |
| const char __user *userbuf, |
| size_t count, loff_t *ppos) |
| { |
| struct ath5k_softc *sc = file->private_data; |
| unsigned int i; |
| char buf[20]; |
| |
| if (copy_from_user(buf, userbuf, min(count, sizeof(buf)))) |
| return -EFAULT; |
| |
| for (i = 0; i < ARRAY_SIZE(dbg_info); i++) { |
| if (strncmp(buf, dbg_info[i].name, |
| strlen(dbg_info[i].name)) == 0) { |
| sc->debug.level ^= dbg_info[i].level; /* toggle bit */ |
| break; |
| } |
| } |
| return count; |
| } |
| |
| static const struct file_operations fops_debug = { |
| .read = read_file_debug, |
| .write = write_file_debug, |
| .open = ath5k_debugfs_open, |
| .owner = THIS_MODULE, |
| }; |
| |
| |
| /* init */ |
| |
| void |
| ath5k_debug_init(void) |
| { |
| ath5k_global_debugfs = debugfs_create_dir("ath5k", NULL); |
| } |
| |
| void |
| ath5k_debug_init_device(struct ath5k_softc *sc) |
| { |
| sc->debug.level = ath5k_debug; |
| |
| sc->debug.debugfs_phydir = debugfs_create_dir(wiphy_name(sc->hw->wiphy), |
| ath5k_global_debugfs); |
| |
| sc->debug.debugfs_debug = debugfs_create_file("debug", 0666, |
| sc->debug.debugfs_phydir, sc, &fops_debug); |
| |
| sc->debug.debugfs_registers = debugfs_create_file("registers", 0444, |
| sc->debug.debugfs_phydir, sc, &fops_registers); |
| |
| sc->debug.debugfs_tsf = debugfs_create_file("tsf", 0666, |
| sc->debug.debugfs_phydir, sc, &fops_tsf); |
| |
| sc->debug.debugfs_beacon = debugfs_create_file("beacon", 0666, |
| sc->debug.debugfs_phydir, sc, &fops_beacon); |
| |
| sc->debug.debugfs_reset = debugfs_create_file("reset", 0222, |
| sc->debug.debugfs_phydir, sc, &fops_reset); |
| } |
| |
| void |
| ath5k_debug_finish(void) |
| { |
| debugfs_remove(ath5k_global_debugfs); |
| } |
| |
| void |
| ath5k_debug_finish_device(struct ath5k_softc *sc) |
| { |
| debugfs_remove(sc->debug.debugfs_debug); |
| debugfs_remove(sc->debug.debugfs_registers); |
| debugfs_remove(sc->debug.debugfs_tsf); |
| debugfs_remove(sc->debug.debugfs_beacon); |
| debugfs_remove(sc->debug.debugfs_reset); |
| debugfs_remove(sc->debug.debugfs_phydir); |
| } |
| |
| |
| /* functions used in other places */ |
| |
| void |
| ath5k_debug_dump_modes(struct ath5k_softc *sc, struct ieee80211_hw_mode *modes) |
| { |
| unsigned int m, i; |
| |
| if (likely(!(sc->debug.level & ATH5K_DEBUG_DUMPMODES))) |
| return; |
| |
| for (m = 0; m < NUM_DRIVER_MODES; m++) { |
| printk(KERN_DEBUG "Mode %u: channels %d, rates %d\n", m, |
| modes[m].num_channels, modes[m].num_rates); |
| printk(KERN_DEBUG " channels:\n"); |
| for (i = 0; i < modes[m].num_channels; i++) |
| printk(KERN_DEBUG " %3d %d %.4x %.4x\n", |
| modes[m].channels[i].chan, |
| modes[m].channels[i].freq, |
| modes[m].channels[i].val, |
| modes[m].channels[i].flag); |
| printk(KERN_DEBUG " rates:\n"); |
| for (i = 0; i < modes[m].num_rates; i++) |
| printk(KERN_DEBUG " %4d %.4x %.4x %.4x\n", |
| modes[m].rates[i].rate, |
| modes[m].rates[i].val, |
| modes[m].rates[i].flags, |
| modes[m].rates[i].val2); |
| } |
| } |
| |
| static inline void |
| ath5k_debug_printrxbuf(struct ath5k_buf *bf, int done) |
| { |
| struct ath5k_desc *ds = bf->desc; |
| |
| printk(KERN_DEBUG "R (%p %llx) %08x %08x %08x %08x %08x %08x %c\n", |
| ds, (unsigned long long)bf->daddr, |
| ds->ds_link, ds->ds_data, ds->ds_ctl0, ds->ds_ctl1, |
| ds->ds_hw[0], ds->ds_hw[1], |
| !done ? ' ' : (ds->ds_rxstat.rs_status == 0) ? '*' : '!'); |
| } |
| |
| void |
| ath5k_debug_printrxbuffs(struct ath5k_softc *sc, struct ath5k_hw *ah) |
| { |
| struct ath5k_desc *ds; |
| struct ath5k_buf *bf; |
| int status; |
| |
| if (likely(!(sc->debug.level & ATH5K_DEBUG_RESET))) |
| return; |
| |
| printk(KERN_DEBUG "rx queue %x, link %p\n", |
| ath5k_hw_get_rx_buf(ah), sc->rxlink); |
| |
| spin_lock_bh(&sc->rxbuflock); |
| list_for_each_entry(bf, &sc->rxbuf, list) { |
| ds = bf->desc; |
| status = ah->ah_proc_rx_desc(ah, ds); |
| if (!status) |
| ath5k_debug_printrxbuf(bf, status == 0); |
| } |
| spin_unlock_bh(&sc->rxbuflock); |
| } |
| |
| void |
| ath5k_debug_dump_skb(struct ath5k_softc *sc, |
| struct sk_buff *skb, const char *prefix, int tx) |
| { |
| char buf[16]; |
| |
| if (likely(!((tx && (sc->debug.level & ATH5K_DEBUG_DUMP_TX)) || |
| (!tx && (sc->debug.level & ATH5K_DEBUG_DUMP_RX))))) |
| return; |
| |
| snprintf(buf, sizeof(buf), "%s %s", wiphy_name(sc->hw->wiphy), prefix); |
| |
| print_hex_dump_bytes(buf, DUMP_PREFIX_NONE, skb->data, |
| min(200U, skb->len)); |
| |
| printk(KERN_DEBUG "\n"); |
| } |
| |
| void |
| ath5k_debug_printtxbuf(struct ath5k_softc *sc, |
| struct ath5k_buf *bf, int done) |
| { |
| struct ath5k_desc *ds = bf->desc; |
| |
| if (likely(!(sc->debug.level & ATH5K_DEBUG_RESET))) |
| return; |
| |
| printk(KERN_DEBUG "T (%p %llx) %08x %08x %08x %08x %08x %08x %08x " |
| "%08x %c\n", ds, (unsigned long long)bf->daddr, ds->ds_link, |
| ds->ds_data, ds->ds_ctl0, ds->ds_ctl1, |
| ds->ds_hw[0], ds->ds_hw[1], ds->ds_hw[2], ds->ds_hw[3], |
| !done ? ' ' : (ds->ds_txstat.ts_status == 0) ? '*' : '!'); |
| } |
| |
| #endif /* if ATH5K_DEBUG */ |