Skip to content

Commit

Permalink
iwlwifi: mvm/pcie: capture last commands on firmware error
Browse files Browse the repository at this point in the history
When a firmware error occurs, capture the last 32 commands
(which are still in memory) in the error dump debugfs file.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
  • Loading branch information
Johannes Berg authored and Emmanuel Grumbach committed May 6, 2014
1 parent 83f32a4 commit 4d07500
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,14 @@
* @IWL_FW_ERROR_DUMP_SRAM:
* @IWL_FW_ERROR_DUMP_REG:
* @IWL_FW_ERROR_DUMP_RXF:
* @IWL_FW_ERROR_DUMP_TXCMD: last TX command data, structured as
* &struct iwl_fw_error_dump_txcmd packets
*/
enum iwl_fw_error_dump_type {
IWL_FW_ERROR_DUMP_SRAM = 0,
IWL_FW_ERROR_DUMP_REG = 1,
IWL_FW_ERROR_DUMP_RXF = 2,
IWL_FW_ERROR_DUMP_TXCMD = 3,

IWL_FW_ERROR_DUMP_MAX,
};
Expand Down Expand Up @@ -105,4 +108,27 @@ struct iwl_fw_error_dump_file {
u8 data[0];
} __packed;

/**
* struct iwl_fw_error_dump_txcmd - TX command data
* @cmdlen: original length of command
* @caplen: captured length of command (may be less)
* @data: captured command data, @caplen bytes
*/
struct iwl_fw_error_dump_txcmd {
__le32 cmdlen;
__le32 caplen;
u8 data[];
} __packed;

/**
* iwl_mvm_fw_error_next_data - advance fw error dump data pointer
* @data: previous data block
* Returns: next data block
*/
static inline struct iwl_fw_error_dump_data *
iwl_mvm_fw_error_next_data(struct iwl_fw_error_dump_data *data)
{
return (void *)(data->data + le32_to_cpu(data->len));
}

#endif /* __fw_error_dump_h__ */
19 changes: 19 additions & 0 deletions drivers/net/wireless/iwlwifi/iwl-trans.h
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,11 @@ struct iwl_trans;
* @unref: release a reference previously taken with @ref. Note that
* initially the reference count is 1, making an initial @unref
* necessary to allow low power states.
* @dump_data: fill a data dump with debug data, maybe containing last
* TX'ed commands and similar. When called with a NULL buffer and
* zero buffer length, provide only the (estimated) required buffer
* length. Return the used buffer length.
* Note that the transport must fill in the proper file headers.
*/
struct iwl_trans_ops {

Expand Down Expand Up @@ -511,6 +516,10 @@ struct iwl_trans_ops {
u32 value);
void (*ref)(struct iwl_trans *trans);
void (*unref)(struct iwl_trans *trans);

#ifdef CONFIG_IWLWIFI_DEBUGFS
u32 (*dump_data)(struct iwl_trans *trans, void *buf, u32 buflen);
#endif
};

/**
Expand Down Expand Up @@ -664,6 +673,16 @@ static inline void iwl_trans_unref(struct iwl_trans *trans)
trans->ops->unref(trans);
}

#ifdef CONFIG_IWLWIFI_DEBUGFS
static inline u32 iwl_trans_dump_data(struct iwl_trans *trans,
void *buf, u32 buflen)
{
if (!trans->ops->dump_data)
return 0;
return trans->ops->dump_data(trans, buf, buflen);
}
#endif

static inline int iwl_trans_send_cmd(struct iwl_trans *trans,
struct iwl_host_cmd *cmd)
{
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/wireless/iwlwifi/mvm/debugfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
#include "iwl-io.h"
#include "iwl-prph.h"
#include "debugfs.h"
#include "fw-error-dump.h"
#include "iwl-fw-error-dump.h"

static ssize_t iwl_dbgfs_tx_flush_write(struct iwl_mvm *mvm, char *buf,
size_t count, loff_t *ppos)
Expand Down
18 changes: 16 additions & 2 deletions drivers/net/wireless/iwlwifi/mvm/ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@
#include "iwl-prph.h"
#include "rs.h"
#include "fw-api-scan.h"
#include "fw-error-dump.h"
#include "time-event.h"
#include "iwl-fw-error-dump.h"

/*
* module name, copyright, version, etc.
Expand Down Expand Up @@ -822,6 +822,7 @@ void iwl_mvm_fw_error_dump(struct iwl_mvm *mvm)
struct iwl_fw_error_dump_file *dump_file;
struct iwl_fw_error_dump_data *dump_data;
u32 file_len;
u32 trans_len;

lockdep_assert_held(&mvm->mutex);

Expand All @@ -833,6 +834,10 @@ void iwl_mvm_fw_error_dump(struct iwl_mvm *mvm)
sizeof(*dump_file) +
sizeof(*dump_data) * 2;

trans_len = iwl_trans_dump_data(mvm->trans, NULL, 0);
if (trans_len)
file_len += trans_len;

dump_file = vmalloc(file_len);
if (!dump_file)
return;
Expand All @@ -846,7 +851,7 @@ void iwl_mvm_fw_error_dump(struct iwl_mvm *mvm)
dump_data->len = cpu_to_le32(mvm->fw_error_rxf_len);
memcpy(dump_data->data, mvm->fw_error_rxf, mvm->fw_error_rxf_len);

dump_data = (void *)((u8 *)dump_data->data + mvm->fw_error_rxf_len);
dump_data = iwl_mvm_fw_error_next_data(dump_data);
dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_SRAM);
dump_data->len = cpu_to_le32(mvm->fw_error_sram_len);

Expand All @@ -864,6 +869,15 @@ void iwl_mvm_fw_error_dump(struct iwl_mvm *mvm)
kfree(mvm->fw_error_sram);
mvm->fw_error_sram = NULL;
mvm->fw_error_sram_len = 0;

if (trans_len) {
void *buf = iwl_mvm_fw_error_next_data(dump_data);
u32 real_trans_len = iwl_trans_dump_data(mvm->trans, buf,
trans_len);
dump_data = (void *)((u8 *)buf + real_trans_len);
dump_file->file_len =
cpu_to_le32(file_len - trans_len + real_trans_len);
}
}
#endif

Expand Down
7 changes: 7 additions & 0 deletions drivers/net/wireless/iwlwifi/pcie/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,13 @@ void iwl_trans_pcie_reclaim(struct iwl_trans *trans, int txq_id, int ssn,
struct sk_buff_head *skbs);
void iwl_trans_pcie_tx_reset(struct iwl_trans *trans);

static inline u16 iwl_pcie_tfd_tb_get_len(struct iwl_tfd *tfd, u8 idx)
{
struct iwl_tfd_tb *tb = &tfd->tbs[idx];

return le16_to_cpu(tb->hi_n_len) >> 4;
}

/*****************************************************
* Error handling
******************************************************/
Expand Down
60 changes: 60 additions & 0 deletions drivers/net/wireless/iwlwifi/pcie/trans.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
#include "iwl-csr.h"
#include "iwl-prph.h"
#include "iwl-agn-hw.h"
#include "iwl-fw-error-dump.h"
#include "internal.h"

static u32 iwl_trans_pcie_read_shr(struct iwl_trans *trans, u32 reg)
Expand Down Expand Up @@ -1669,6 +1670,61 @@ static int iwl_trans_pcie_dbgfs_register(struct iwl_trans *trans,
IWL_ERR(trans, "failed to create the trans debugfs entry\n");
return -ENOMEM;
}

static u32 iwl_trans_pcie_get_cmdlen(struct iwl_tfd *tfd)
{
u32 cmdlen = 0;
int i;

for (i = 0; i < IWL_NUM_OF_TBS; i++)
cmdlen += iwl_pcie_tfd_tb_get_len(tfd, i);

return cmdlen;
}

static u32 iwl_trans_pcie_dump_data(struct iwl_trans *trans,
void *buf, u32 buflen)
{
struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
struct iwl_fw_error_dump_data *data;
struct iwl_txq *cmdq = &trans_pcie->txq[trans_pcie->cmd_queue];
struct iwl_fw_error_dump_txcmd *txcmd;
u32 len;
int i, ptr;

if (!buf)
return sizeof(*data) +
cmdq->q.n_window * (sizeof(*txcmd) +
TFD_MAX_PAYLOAD_SIZE);

len = 0;
data = buf;
data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_TXCMD);
txcmd = (void *)data->data;
spin_lock_bh(&cmdq->lock);
ptr = cmdq->q.write_ptr;
for (i = 0; i < cmdq->q.n_window; i++) {
u8 idx = get_cmd_index(&cmdq->q, ptr);
u32 caplen, cmdlen;

cmdlen = iwl_trans_pcie_get_cmdlen(&cmdq->tfds[ptr]);
caplen = min_t(u32, TFD_MAX_PAYLOAD_SIZE, cmdlen);

if (cmdlen) {
len += sizeof(*txcmd) + caplen;
txcmd->cmdlen = cpu_to_le32(cmdlen);
txcmd->caplen = cpu_to_le32(caplen);
memcpy(txcmd->data, cmdq->entries[idx].cmd, caplen);
txcmd = (void *)((u8 *)txcmd->data + caplen);
}

ptr = iwl_queue_dec_wrap(ptr);
}
spin_unlock_bh(&cmdq->lock);

data->len = cpu_to_le32(len);
return sizeof(*data) + len;
}
#else
static int iwl_trans_pcie_dbgfs_register(struct iwl_trans *trans,
struct dentry *dir)
Expand Down Expand Up @@ -1711,6 +1767,10 @@ static const struct iwl_trans_ops trans_ops_pcie = {
.grab_nic_access = iwl_trans_pcie_grab_nic_access,
.release_nic_access = iwl_trans_pcie_release_nic_access,
.set_bits_mask = iwl_trans_pcie_set_bits_mask,

#ifdef CONFIG_IWLWIFI_DEBUGFS
.dump_data = iwl_trans_pcie_dump_data,
#endif
};

struct iwl_trans *iwl_trans_pcie_alloc(struct pci_dev *pdev,
Expand Down
40 changes: 22 additions & 18 deletions drivers/net/wireless/iwlwifi/pcie/tx.c
Original file line number Diff line number Diff line change
Expand Up @@ -353,13 +353,6 @@ static inline dma_addr_t iwl_pcie_tfd_tb_get_addr(struct iwl_tfd *tfd, u8 idx)
return addr;
}

static inline u16 iwl_pcie_tfd_tb_get_len(struct iwl_tfd *tfd, u8 idx)
{
struct iwl_tfd_tb *tb = &tfd->tbs[idx];

return le16_to_cpu(tb->hi_n_len) >> 4;
}

static inline void iwl_pcie_tfd_set_tb(struct iwl_tfd *tfd, u8 idx,
dma_addr_t addr, u16 len)
{
Expand Down Expand Up @@ -1322,28 +1315,39 @@ static int iwl_pcie_enqueue_hcmd(struct iwl_trans *trans,
cmd_pos = offsetof(struct iwl_device_cmd, payload);
copy_size = sizeof(out_cmd->hdr);
for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) {
int copy = 0;
int copy;

if (!cmd->len[i])
continue;

/* need at least IWL_HCMD_SCRATCHBUF_SIZE copied */
if (copy_size < IWL_HCMD_SCRATCHBUF_SIZE) {
copy = IWL_HCMD_SCRATCHBUF_SIZE - copy_size;

if (copy > cmd->len[i])
copy = cmd->len[i];
}

/* copy everything if not nocopy/dup */
if (!(cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY |
IWL_HCMD_DFL_DUP)))
IWL_HCMD_DFL_DUP))) {
copy = cmd->len[i];

if (copy) {
memcpy((u8 *)out_cmd + cmd_pos, cmd->data[i], copy);
cmd_pos += copy;
copy_size += copy;
continue;
}

/*
* Otherwise we need at least IWL_HCMD_SCRATCHBUF_SIZE copied
* in total (for the scratchbuf handling), but copy up to what
* we can fit into the payload for debug dump purposes.
*/
copy = min_t(int, TFD_MAX_PAYLOAD_SIZE - cmd_pos, cmd->len[i]);

memcpy((u8 *)out_cmd + cmd_pos, cmd->data[i], copy);
cmd_pos += copy;

/* However, treat copy_size the proper way, we need it below */
if (copy_size < IWL_HCMD_SCRATCHBUF_SIZE) {
copy = IWL_HCMD_SCRATCHBUF_SIZE - copy_size;

if (copy > cmd->len[i])
copy = cmd->len[i];
copy_size += copy;
}
}

Expand Down

0 comments on commit 4d07500

Please sign in to comment.