mirror of https://gitee.com/openkylin/linux.git
iwlwifi: mvm/pcie: capture last commands on firmware error
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>
This commit is contained in:
parent
83f32a4b4a
commit
4d075007d6
|
@ -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,
|
||||
};
|
||||
|
@ -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__ */
|
|
@ -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 {
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
******************************************************/
|
||||
|
|
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
@ -1322,27 +1315,38 @@ 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 */
|
||||
/* copy everything if not nocopy/dup */
|
||||
if (!(cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY |
|
||||
IWL_HCMD_DFL_DUP))) {
|
||||
copy = cmd->len[i];
|
||||
|
||||
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 everything if not nocopy/dup */
|
||||
if (!(cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY |
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue