[PATCH 1/4] ath10k: provide firmware crash info via debugfs.

Kalle Valo kvalo at qca.qualcomm.com
Thu Jun 5 09:18:12 PDT 2014


greearb at candelatech.com writes:

> From: Ben Greear <greearb at candelatech.com>
>
> Store the firmware crash registers and last 128 or so
> firmware debug-log ids and present them to user-space
> via debugfs.
>
> Should help with figuring out why the firmware crashed.
>
> Signed-off-by: Ben Greear <greearb at candelatech.com>

My first comments, more later. But my first impression is that I like
this, we just need to sort out some implementation issues.

kbuild found some warnings:

drivers/net/wireless/ath/ath10k/debug.c:725:2: error: implicit declaration of function 'vfree' [-Werror=implicit-function-declaration]
drivers/net/wireless/ath/ath10k/debug.c:624:2: error: implicit declaration of function 'vmalloc' [-Werror=implicit-function-declaration]
drivers/net/wireless/ath/ath10k/debug.c:624:6: warning: assignment makes pointer from integer without a cast [enabled by default]

The naming of structures and functions are a bit too long, but I'll
comment on that later. That's easy to change as the last thing.

> +/**
> + * enum ath10k_fw_error_dump_type - types of data in the dump file
> + * @ATH10K_FW_ERROR_DUMP_DBGLOG:  Recent firmware debug log entries
> + * @ATH10K_FW_ERROR_DUMP_CRASH:   Crash dump in binary format
> + */
> +enum ath10k_fw_error_dump_type {
> +	ATH10K_FW_ERROR_DUMP_DBGLOG = 0,
> +	ATH10K_FW_ERROR_DUMP_REGDUMP = 1,
> +
> +	ATH10K_FW_ERROR_DUMP_MAX,
> +};
> +
> +

Extra newline.

> +struct ath10k_tlv_dump_data {
> +	u32 type; /* see ath10k_fw_error_dump_type above */
> +	u32 tlv_len; /* in bytes */
> +	u8 tlv_data[]; /* Pad to 32-bit boundaries as needed. */
> +} __packed;
> +
> +struct ath10k_dump_file_data {
> +	/* Dump file information */
> +	u32 len;
> +	u32 magic; /* 0x01020304, tells us byte-order of host if we care */
> +	u32 version; /* File dump version, 1 for now. */

Actually I would prefer to have magic first and have ASCII string as
string, for example "ATH10K-FW-DUMP".

> +	/* Some info we can get from ath10k struct that might help. */
> +	u32 chip_id;
> +	u32 target_version;

bus_type or something like that would be good to add already now.

> +	u8 fw_version_major;
> +	u8 unused8; /* pad fw_version_major */
> +	u16 unused16; /* pad fw_version_major */

I don't see any point of using u8 or u16. Would it be simpler just to
use u32 for everything?

> +	u32 fw_version_minor;
> +	u16 fw_version_release;
> +	u16 fw_version_build;
> +	u32 phy_capability;
> +	u32 hw_min_tx_power;
> +	u32 hw_max_tx_power;
> +	u32 ht_cap_info;
> +	u32 vht_cap_info;
> +	u32 num_rf_chains;
> +	char fw_ver[64]; /* Firmware version string */

Can we reuse ETHTOOL_FWVERS_LEN? cfg80211 already uses that.

#define ETHTOOL_FWVERS_LEN	32

> +	/* Kernel related information */
> +	u32 kernel_ver_code; /* LINUX_VERSION_CODE */
> +	char kernel_ver[64]; /* VERMAGIC_STRING */
> +
> +	u8 unused[64]; /* Room for growth w/out changing binary format */

Maybe 128 bytes, just so that we don't need to change it for some time?

> +	struct ath10k_tlv_dump_data data; /* more may follow */

I would prefer:

u8 data[0];

So that this struct is only about the header.

> +} __packed;
> +
> +/* This will store at least the last 128 entries.  Each dbglog message
> + * is a max of 7 32-bit integers in length, but the length can be less
> + * than that as well.
> + */
> +#define ATH10K_DBGLOG_DATA_LEN (128 * 7 * 4)

Empty line after the define.

> @@ -488,6 +555,12 @@ struct ath10k {
>  
>  	struct dfs_pattern_detector *dfs_detector;
>  
> +	/* Used for crash-dump storage */
> +	/* Don't over-write dump info until someone reads the data. */
> +	bool crashed_since_read;
> +	struct ath10k_dbglog_entry_storage dbglog_entry_data;
> +	u32 reg_dump_values[REG_DUMP_COUNT_QCA988X];

I think these should be in struct ath10k_debug.

> diff --git a/drivers/net/wireless/ath/ath10k/debug.c b/drivers/net/wireless/ath/ath10k/debug.c
> index 1b7ff4b..a7d7877 100644
> --- a/drivers/net/wireless/ath/ath10k/debug.c
> +++ b/drivers/net/wireless/ath/ath10k/debug.c
> @@ -17,6 +17,8 @@
>  
>  #include <linux/module.h>
>  #include <linux/debugfs.h>
> +#include <linux/version.h>
> +#include <linux/vermagic.h>
>  
>  #include "core.h"
>  #include "debug.h"
> @@ -577,6 +579,145 @@ static const struct file_operations fops_chip_id = {
>  	.llseek = default_llseek,
>  };
>  
> +void ath10k_dbg_save_fw_dbg_buffer(struct ath10k *ar, u8 *buffer, int len)
> +{
> +	int i;
> +	int z = ar->dbglog_entry_data.next_idx;
> +
> +	/* Don't save any new logs until user-space reads this. */
> +	if (ar->crashed_since_read)
> +		return;

Locking? If this functions depends on something, please document that
with lockdep_assert_held().

> +	for (i = 0; i < len; i++) {
> +		ar->dbglog_entry_data.data[z] = buffer[i];
> +		z++;
> +		if (z >= ATH10K_DBGLOG_DATA_LEN)
> +			z = 0;
> +	}

Empty line here.

> +	ar->dbglog_entry_data.next_idx = z;
> +}
> +EXPORT_SYMBOL(ath10k_dbg_save_fw_dbg_buffer);
> +
> +static struct ath10k_dump_file_data *ath10k_build_dump_file(struct ath10k *ar)
> +{
> +	unsigned int len = (sizeof(ar->dbglog_entry_data)

Unneeded parenthesis.

> +			    + sizeof(ar->reg_dump_values));
> +	unsigned int sofar = 0;
> +	char *buf;
> +	struct ath10k_tlv_dump_data *dump_tlv;
> +	struct ath10k_dump_file_data *dump_data;
> +	int hdr_len = sizeof(*dump_data) - sizeof(dump_data->data);

I prefer separating variable declarations and definitions.

> +
> +	lockdep_assert_held(&ar->conf_mutex);
> +
> +	len += hdr_len;
> +	sofar += hdr_len;
> +
> +	/* So we can add headers to the data dump */
> +	len += 2 * sizeof(*dump_tlv);

This is a bit awkward way of defining the length. Something like this
would be easier to read:

len = sizeof (*dump_tlv) + sizeof(ar->dbglog_entry_data);
len +=	sizeof (*dump_tlv) + sizeof(ar->reg_dump_values));

> +
> +	/* This is going to get big when we start dumping FW RAM and such,
> +	 * so go ahead and use vmalloc.
> +	 */
> +	buf = vmalloc(len);
> +	if (!buf)
> +		return NULL;
> +
> +	memset(buf, 0, len);
> +	dump_data = (struct ath10k_dump_file_data *)(buf);
> +	dump_data->len = len;
> +	dump_data->magic = 0x01020304;
> +	dump_data->version = 1;
> +	dump_data->chip_id = ar->chip_id;
> +	dump_data->target_version = ar->target_version;
> +	dump_data->fw_version_major = ar->fw_version_major;
> +	dump_data->fw_version_minor = ar->fw_version_minor;
> +	dump_data->fw_version_release = ar->fw_version_release;
> +	dump_data->fw_version_build = ar->fw_version_build;
> +	dump_data->phy_capability = ar->phy_capability;
> +	dump_data->hw_min_tx_power = ar->hw_min_tx_power;
> +	dump_data->hw_max_tx_power = ar->hw_max_tx_power;
> +	dump_data->ht_cap_info = ar->ht_cap_info;
> +	dump_data->vht_cap_info = ar->vht_cap_info;
> +	dump_data->num_rf_chains = ar->num_rf_chains;
> +
> +	strncpy(dump_data->fw_ver, ar->hw->wiphy->fw_version,
> +		sizeof(dump_data->fw_ver) - 1);

BUILD_BUG_ON(sizeof(dump_data->fw_ver) != sizeof(ar->hw->wiphy->fw_version))?

And wouldn't strlcpy() be safer?

> +	dump_data->kernel_ver_code = LINUX_VERSION_CODE;

checkpatch actually complains about this:

drivers/net/wireless/ath/ath10k/debug.c:649: WARNING: LINUX_VERSION_CODE should be avoided, code should be for the version to which it is merged

But I guess there's nothing we can do for that.

> +	strncpy(dump_data->kernel_ver, VERMAGIC_STRING,
> +		sizeof(dump_data->kernel_ver) - 1);

strlcpy()?

> +	/* Gather dbg-log */
> +	dump_tlv = (struct ath10k_tlv_dump_data *)(buf + sofar);
> +	dump_tlv->type = ATH10K_FW_ERROR_DUMP_DBGLOG;
> +	dump_tlv->tlv_len = sizeof(ar->dbglog_entry_data);
> +	memcpy(dump_tlv->tlv_data, &ar->dbglog_entry_data, dump_tlv->tlv_len);
> +	sofar += sizeof(*dump_tlv) + dump_tlv->tlv_len;
> +
> +	/* Gather crash-dump */
> +	dump_tlv = (struct ath10k_tlv_dump_data *)(buf + sofar);
> +	dump_tlv->type = ATH10K_FW_ERROR_DUMP_REGDUMP;
> +	dump_tlv->tlv_len = sizeof(ar->reg_dump_values);
> +	memcpy(dump_tlv->tlv_data, &ar->reg_dump_values, dump_tlv->tlv_len);
> +	sofar += sizeof(*dump_tlv) + dump_tlv->tlv_len;
> +
> +	return dump_data;
> +}
> +
> +static int ath10k_fw_error_dump_open(struct inode *inode, struct file *file)
> +{
> +	struct ath10k *ar = inode->i_private;
> +	int ret;
> +	struct ath10k_dump_file_data *dump;
> +
> +	if (!ar)
> +		return -EINVAL;

Is this cheak necessary? In what cases is i_private NULL?

> --- a/drivers/net/wireless/ath/ath10k/debug.h
> +++ b/drivers/net/wireless/ath/ath10k/debug.h
> @@ -19,6 +19,7 @@
>  #define _DEBUG_H_
>  
>  #include <linux/types.h>
> +#include "pci.h"

debug.h shouldn't access pci.h directly. All access should happen
through hif.

>  
>  enum ath10k_debug_mask {
> @@ -110,4 +111,28 @@ static inline void ath10k_dbg_dump(enum ath10k_debug_mask mask,
>  {
>  }
>  #endif /* CONFIG_ATH10K_DEBUG */
> +
> +
> +/* Target debug log related defines and structs */
> +
> +/* Target is 32-bit CPU, so we just use u32 for
> + * the pointers.  The memory space is relative to the
> + * target, not the host.
> + */
> +struct ath10k_fw_dbglog_buf {
> +	u32 next; /* pointer to dblog_buf_s. */
> +	u32 buffer; /* pointer to u8 buffer */
> +	u32 bufsize;
> +	u32 length;
> +	u32 count;
> +	u32 free;
> +} __packed;
> +
> +struct ath10k_fw_dbglog_hdr {
> +	u32 dbuf; /* pointer to dbglog_buf_s */
> +	u32 dropped;
> +} __packed;

Should these be in hw.h?

> --- a/drivers/net/wireless/ath/ath10k/pci.c
> +++ b/drivers/net/wireless/ath/ath10k/pci.c
> @@ -19,7 +19,7 @@
>  #include <linux/module.h>
>  #include <linux/interrupt.h>
>  #include <linux/spinlock.h>
> -#include <linux/bitops.h>

Why remove bitops.h?

Have to stop here, more later.

-- 
Kalle Valo



More information about the ath10k mailing list