f7f7cc47fc
This is just a cleanup and doesn't change how the code works. debugfs_create_dir() and debugfs_create_file() return an error pointer (-ENODEV) if CONFIG_DEBUG_FS is not enabled, otherwise if an error occurs they return NULL. This is how they are implemented and what it says in the DebugFS documentation. DebugFS can not be compiled as a module. As a result, we only need to check for error pointers and particularly -ENODEV one time to know that DebugFS is enabled. This patch keeps the first check for error pointers and removes the rest. The other reason for this patch, is that it silences some Smatch warnings. Smatch sees the condition "(result != -ENODEV)" and assumes that it's possible for "result" to equal -ENODEV. If it were possible it would lead to an error pointer dereference. But since it's not, we can just remove the check. Signed-off-by: Dan Carpenter <error27@gmail.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
516 lines
14 KiB
C
516 lines
14 KiB
C
/*
|
|
* Intel Wireless Multicomm 3200 WiFi driver
|
|
*
|
|
* Copyright (C) 2009 Intel Corporation <ilw@linux.intel.com>
|
|
* Samuel Ortiz <samuel.ortiz@intel.com>
|
|
* Zhu Yi <yi.zhu@intel.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License version
|
|
* 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/debugfs.h>
|
|
|
|
#include "iwm.h"
|
|
#include "bus.h"
|
|
#include "rx.h"
|
|
#include "debug.h"
|
|
|
|
static struct {
|
|
u8 id;
|
|
char *name;
|
|
} iwm_debug_module[__IWM_DM_NR] = {
|
|
{IWM_DM_BOOT, "boot"},
|
|
{IWM_DM_FW, "fw"},
|
|
{IWM_DM_SDIO, "sdio"},
|
|
{IWM_DM_NTF, "ntf"},
|
|
{IWM_DM_RX, "rx"},
|
|
{IWM_DM_TX, "tx"},
|
|
{IWM_DM_MLME, "mlme"},
|
|
{IWM_DM_CMD, "cmd"},
|
|
{IWM_DM_WEXT, "wext"},
|
|
};
|
|
|
|
#define add_dbg_module(dbg, name, id, initlevel) \
|
|
do { \
|
|
struct dentry *d; \
|
|
dbg.dbg_module[id] = (initlevel); \
|
|
d = debugfs_create_x8(name, 0600, dbg.dbgdir, \
|
|
&(dbg.dbg_module[id])); \
|
|
if (!IS_ERR(d)) \
|
|
dbg.dbg_module_dentries[id] = d; \
|
|
} while (0)
|
|
|
|
static int iwm_debugfs_u32_read(void *data, u64 *val)
|
|
{
|
|
struct iwm_priv *iwm = data;
|
|
|
|
*val = iwm->dbg.dbg_level;
|
|
return 0;
|
|
}
|
|
|
|
static int iwm_debugfs_dbg_level_write(void *data, u64 val)
|
|
{
|
|
struct iwm_priv *iwm = data;
|
|
int i;
|
|
|
|
iwm->dbg.dbg_level = val;
|
|
|
|
for (i = 0; i < __IWM_DM_NR; i++)
|
|
iwm->dbg.dbg_module[i] = val;
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(fops_iwm_dbg_level,
|
|
iwm_debugfs_u32_read, iwm_debugfs_dbg_level_write,
|
|
"%llu\n");
|
|
|
|
static int iwm_debugfs_dbg_modules_write(void *data, u64 val)
|
|
{
|
|
struct iwm_priv *iwm = data;
|
|
int i, bit;
|
|
|
|
iwm->dbg.dbg_modules = val;
|
|
|
|
for (i = 0; i < __IWM_DM_NR; i++)
|
|
iwm->dbg.dbg_module[i] = 0;
|
|
|
|
for_each_bit(bit, &iwm->dbg.dbg_modules, __IWM_DM_NR)
|
|
iwm->dbg.dbg_module[bit] = iwm->dbg.dbg_level;
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(fops_iwm_dbg_modules,
|
|
iwm_debugfs_u32_read, iwm_debugfs_dbg_modules_write,
|
|
"%llu\n");
|
|
|
|
static int iwm_generic_open(struct inode *inode, struct file *filp)
|
|
{
|
|
filp->private_data = inode->i_private;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static ssize_t iwm_debugfs_txq_read(struct file *filp, char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct iwm_priv *iwm = filp->private_data;
|
|
char *buf;
|
|
int i, buf_len = 4096;
|
|
size_t len = 0;
|
|
ssize_t ret;
|
|
|
|
if (*ppos != 0)
|
|
return 0;
|
|
if (count < sizeof(buf))
|
|
return -ENOSPC;
|
|
|
|
buf = kzalloc(buf_len, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < IWM_TX_QUEUES; i++) {
|
|
struct iwm_tx_queue *txq = &iwm->txq[i];
|
|
struct sk_buff *skb;
|
|
int j;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&txq->queue.lock, flags);
|
|
|
|
skb = (struct sk_buff *)&txq->queue;
|
|
|
|
len += snprintf(buf + len, buf_len - len, "TXQ #%d\n", i);
|
|
len += snprintf(buf + len, buf_len - len, "\tStopped: %d\n",
|
|
__netif_subqueue_stopped(iwm_to_ndev(iwm),
|
|
txq->id));
|
|
len += snprintf(buf + len, buf_len - len, "\tConcat count:%d\n",
|
|
txq->concat_count);
|
|
len += snprintf(buf + len, buf_len - len, "\tQueue len: %d\n",
|
|
skb_queue_len(&txq->queue));
|
|
for (j = 0; j < skb_queue_len(&txq->queue); j++) {
|
|
struct iwm_tx_info *tx_info;
|
|
|
|
skb = skb->next;
|
|
tx_info = skb_to_tx_info(skb);
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tSKB #%d\n", j);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\t\tsta: %d\n", tx_info->sta);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\t\tcolor: %d\n", tx_info->color);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\t\ttid: %d\n", tx_info->tid);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&txq->queue.lock, flags);
|
|
|
|
spin_lock_irqsave(&txq->stopped_queue.lock, flags);
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tStopped Queue len: %d\n",
|
|
skb_queue_len(&txq->stopped_queue));
|
|
for (j = 0; j < skb_queue_len(&txq->stopped_queue); j++) {
|
|
struct iwm_tx_info *tx_info;
|
|
|
|
skb = skb->next;
|
|
tx_info = skb_to_tx_info(skb);
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tSKB #%d\n", j);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\t\tsta: %d\n", tx_info->sta);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\t\tcolor: %d\n", tx_info->color);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\t\ttid: %d\n", tx_info->tid);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&txq->stopped_queue.lock, flags);
|
|
}
|
|
|
|
ret = simple_read_from_buffer(buffer, len, ppos, buf, buf_len);
|
|
kfree(buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t iwm_debugfs_tx_credit_read(struct file *filp,
|
|
char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct iwm_priv *iwm = filp->private_data;
|
|
struct iwm_tx_credit *credit = &iwm->tx_credit;
|
|
char *buf;
|
|
int i, buf_len = 4096;
|
|
size_t len = 0;
|
|
ssize_t ret;
|
|
|
|
if (*ppos != 0)
|
|
return 0;
|
|
if (count < sizeof(buf))
|
|
return -ENOSPC;
|
|
|
|
buf = kzalloc(buf_len, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"NR pools: %d\n", credit->pool_nr);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"pools map: 0x%lx\n", credit->full_pools_map);
|
|
|
|
len += snprintf(buf + len, buf_len - len, "\n### POOLS ###\n");
|
|
for (i = 0; i < IWM_MACS_OUT_GROUPS; i++) {
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"pools entry #%d\n", i);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tid: %d\n",
|
|
credit->pools[i].id);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tsid: %d\n",
|
|
credit->pools[i].sid);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tmin_pages: %d\n",
|
|
credit->pools[i].min_pages);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tmax_pages: %d\n",
|
|
credit->pools[i].max_pages);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\talloc_pages: %d\n",
|
|
credit->pools[i].alloc_pages);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tfreed_pages: %d\n",
|
|
credit->pools[i].total_freed_pages);
|
|
}
|
|
|
|
len += snprintf(buf + len, buf_len - len, "\n### SPOOLS ###\n");
|
|
for (i = 0; i < IWM_MACS_OUT_SGROUPS; i++) {
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"spools entry #%d\n", i);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tid: %d\n",
|
|
credit->spools[i].id);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tmax_pages: %d\n",
|
|
credit->spools[i].max_pages);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\talloc_pages: %d\n",
|
|
credit->spools[i].alloc_pages);
|
|
|
|
}
|
|
|
|
ret = simple_read_from_buffer(buffer, len, ppos, buf, buf_len);
|
|
kfree(buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t iwm_debugfs_rx_ticket_read(struct file *filp,
|
|
char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct iwm_priv *iwm = filp->private_data;
|
|
struct iwm_rx_ticket_node *ticket;
|
|
char *buf;
|
|
int buf_len = 4096, i;
|
|
size_t len = 0;
|
|
ssize_t ret;
|
|
|
|
if (*ppos != 0)
|
|
return 0;
|
|
if (count < sizeof(buf))
|
|
return -ENOSPC;
|
|
|
|
buf = kzalloc(buf_len, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
spin_lock(&iwm->ticket_lock);
|
|
list_for_each_entry(ticket, &iwm->rx_tickets, node) {
|
|
len += snprintf(buf + len, buf_len - len, "Ticket #%d\n",
|
|
ticket->ticket->id);
|
|
len += snprintf(buf + len, buf_len - len, "\taction: 0x%x\n",
|
|
ticket->ticket->action);
|
|
len += snprintf(buf + len, buf_len - len, "\tflags: 0x%x\n",
|
|
ticket->ticket->flags);
|
|
}
|
|
spin_unlock(&iwm->ticket_lock);
|
|
|
|
for (i = 0; i < IWM_RX_ID_HASH; i++) {
|
|
struct iwm_rx_packet *packet;
|
|
struct list_head *pkt_list = &iwm->rx_packets[i];
|
|
|
|
if (!list_empty(pkt_list)) {
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"Packet hash #%d\n", i);
|
|
spin_lock(&iwm->packet_lock[i]);
|
|
list_for_each_entry(packet, pkt_list, node) {
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tPacket id: %d\n",
|
|
packet->id);
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tPacket length: %lu\n",
|
|
packet->pkt_size);
|
|
}
|
|
spin_unlock(&iwm->packet_lock[i]);
|
|
}
|
|
}
|
|
|
|
ret = simple_read_from_buffer(buffer, len, ppos, buf, buf_len);
|
|
kfree(buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t iwm_debugfs_fw_err_read(struct file *filp,
|
|
char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
|
|
struct iwm_priv *iwm = filp->private_data;
|
|
char buf[512];
|
|
int buf_len = 512;
|
|
size_t len = 0;
|
|
|
|
if (*ppos != 0)
|
|
return 0;
|
|
if (count < sizeof(buf))
|
|
return -ENOSPC;
|
|
|
|
if (!iwm->last_fw_err)
|
|
return -ENOMEM;
|
|
|
|
if (iwm->last_fw_err->line_num == 0)
|
|
goto out;
|
|
|
|
len += snprintf(buf + len, buf_len - len, "%cMAC FW ERROR:\n",
|
|
(le32_to_cpu(iwm->last_fw_err->category) == UMAC_SYS_ERR_CAT_LMAC)
|
|
? 'L' : 'U');
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tCategory: %d\n",
|
|
le32_to_cpu(iwm->last_fw_err->category));
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tStatus: 0x%x\n",
|
|
le32_to_cpu(iwm->last_fw_err->status));
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tPC: 0x%x\n",
|
|
le32_to_cpu(iwm->last_fw_err->pc));
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tblink1: %d\n",
|
|
le32_to_cpu(iwm->last_fw_err->blink1));
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tblink2: %d\n",
|
|
le32_to_cpu(iwm->last_fw_err->blink2));
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tilink1: %d\n",
|
|
le32_to_cpu(iwm->last_fw_err->ilink1));
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tilink2: %d\n",
|
|
le32_to_cpu(iwm->last_fw_err->ilink2));
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tData1: 0x%x\n",
|
|
le32_to_cpu(iwm->last_fw_err->data1));
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tData2: 0x%x\n",
|
|
le32_to_cpu(iwm->last_fw_err->data2));
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tLine number: %d\n",
|
|
le32_to_cpu(iwm->last_fw_err->line_num));
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tUMAC status: 0x%x\n",
|
|
le32_to_cpu(iwm->last_fw_err->umac_status));
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tLMAC status: 0x%x\n",
|
|
le32_to_cpu(iwm->last_fw_err->lmac_status));
|
|
|
|
len += snprintf(buf + len, buf_len - len,
|
|
"\tSDIO status: 0x%x\n",
|
|
le32_to_cpu(iwm->last_fw_err->sdio_status));
|
|
|
|
out:
|
|
|
|
return simple_read_from_buffer(buffer, len, ppos, buf, buf_len);
|
|
}
|
|
|
|
static const struct file_operations iwm_debugfs_txq_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = iwm_generic_open,
|
|
.read = iwm_debugfs_txq_read,
|
|
};
|
|
|
|
static const struct file_operations iwm_debugfs_tx_credit_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = iwm_generic_open,
|
|
.read = iwm_debugfs_tx_credit_read,
|
|
};
|
|
|
|
static const struct file_operations iwm_debugfs_rx_ticket_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = iwm_generic_open,
|
|
.read = iwm_debugfs_rx_ticket_read,
|
|
};
|
|
|
|
static const struct file_operations iwm_debugfs_fw_err_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = iwm_generic_open,
|
|
.read = iwm_debugfs_fw_err_read,
|
|
};
|
|
|
|
int iwm_debugfs_init(struct iwm_priv *iwm)
|
|
{
|
|
int i, result;
|
|
char devdir[16];
|
|
|
|
iwm->dbg.rootdir = debugfs_create_dir(KBUILD_MODNAME, NULL);
|
|
result = PTR_ERR(iwm->dbg.rootdir);
|
|
if (!result || IS_ERR(iwm->dbg.rootdir)) {
|
|
if (result == -ENODEV) {
|
|
IWM_ERR(iwm, "DebugFS (CONFIG_DEBUG_FS) not "
|
|
"enabled in kernel config\n");
|
|
result = 0; /* No debugfs support */
|
|
}
|
|
IWM_ERR(iwm, "Couldn't create rootdir: %d\n", result);
|
|
goto error;
|
|
}
|
|
|
|
snprintf(devdir, sizeof(devdir), "%s", wiphy_name(iwm_to_wiphy(iwm)));
|
|
|
|
iwm->dbg.devdir = debugfs_create_dir(devdir, iwm->dbg.rootdir);
|
|
iwm->dbg.dbgdir = debugfs_create_dir("debug", iwm->dbg.devdir);
|
|
iwm->dbg.rxdir = debugfs_create_dir("rx", iwm->dbg.devdir);
|
|
iwm->dbg.txdir = debugfs_create_dir("tx", iwm->dbg.devdir);
|
|
iwm->dbg.busdir = debugfs_create_dir("bus", iwm->dbg.devdir);
|
|
if (iwm->bus_ops->debugfs_init) {
|
|
result = iwm->bus_ops->debugfs_init(iwm, iwm->dbg.busdir);
|
|
if (result < 0) {
|
|
IWM_ERR(iwm, "Couldn't create bus entry: %d\n", result);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
iwm->dbg.dbg_level = IWM_DL_NONE;
|
|
iwm->dbg.dbg_level_dentry =
|
|
debugfs_create_file("level", 0200, iwm->dbg.dbgdir, iwm,
|
|
&fops_iwm_dbg_level);
|
|
|
|
iwm->dbg.dbg_modules = IWM_DM_DEFAULT;
|
|
iwm->dbg.dbg_modules_dentry =
|
|
debugfs_create_file("modules", 0200, iwm->dbg.dbgdir, iwm,
|
|
&fops_iwm_dbg_modules);
|
|
|
|
for (i = 0; i < __IWM_DM_NR; i++)
|
|
add_dbg_module(iwm->dbg, iwm_debug_module[i].name,
|
|
iwm_debug_module[i].id, IWM_DL_DEFAULT);
|
|
|
|
iwm->dbg.txq_dentry = debugfs_create_file("queues", 0200,
|
|
iwm->dbg.txdir, iwm,
|
|
&iwm_debugfs_txq_fops);
|
|
|
|
iwm->dbg.tx_credit_dentry = debugfs_create_file("credits", 0200,
|
|
iwm->dbg.txdir, iwm,
|
|
&iwm_debugfs_tx_credit_fops);
|
|
|
|
iwm->dbg.rx_ticket_dentry = debugfs_create_file("tickets", 0200,
|
|
iwm->dbg.rxdir, iwm,
|
|
&iwm_debugfs_rx_ticket_fops);
|
|
|
|
iwm->dbg.fw_err_dentry = debugfs_create_file("last_fw_err", 0200,
|
|
iwm->dbg.dbgdir, iwm,
|
|
&iwm_debugfs_fw_err_fops);
|
|
|
|
return 0;
|
|
|
|
error:
|
|
return result;
|
|
}
|
|
|
|
void iwm_debugfs_exit(struct iwm_priv *iwm)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < __IWM_DM_NR; i++)
|
|
debugfs_remove(iwm->dbg.dbg_module_dentries[i]);
|
|
|
|
debugfs_remove(iwm->dbg.dbg_modules_dentry);
|
|
debugfs_remove(iwm->dbg.dbg_level_dentry);
|
|
debugfs_remove(iwm->dbg.txq_dentry);
|
|
debugfs_remove(iwm->dbg.tx_credit_dentry);
|
|
debugfs_remove(iwm->dbg.rx_ticket_dentry);
|
|
debugfs_remove(iwm->dbg.fw_err_dentry);
|
|
if (iwm->bus_ops->debugfs_exit)
|
|
iwm->bus_ops->debugfs_exit(iwm);
|
|
|
|
debugfs_remove(iwm->dbg.busdir);
|
|
debugfs_remove(iwm->dbg.dbgdir);
|
|
debugfs_remove(iwm->dbg.txdir);
|
|
debugfs_remove(iwm->dbg.rxdir);
|
|
debugfs_remove(iwm->dbg.devdir);
|
|
debugfs_remove(iwm->dbg.rootdir);
|
|
}
|