diff mbox

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

Message ID 1401812719-25061-1-git-send-email-greearb@candelatech.com (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Ben Greear June 3, 2014, 4:25 p.m. UTC
From: Ben Greear <greearb@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@candelatech.com>
---

This series is compile-tested only at this point.  Hoping
for feedback on general approach at least.
 

drivers/net/wireless/ath/ath10k/core.h  |  41 +++++++++++
 drivers/net/wireless/ath/ath10k/debug.c | 124 ++++++++++++++++++++++++++++++++
 drivers/net/wireless/ath/ath10k/debug.h |  25 +++++++
 drivers/net/wireless/ath/ath10k/pci.c   |  86 +++++++++++++++++++++-
 4 files changed, 273 insertions(+), 3 deletions(-)

Comments

Michal Kazior June 4, 2014, 9:04 a.m. UTC | #1
On 3 June 2014 18:25,  <greearb@candelatech.com> wrote:
> From: Ben Greear <greearb@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@candelatech.com>
> ---
>
> This series is compile-tested only at this point.  Hoping
> for feedback on general approach at least.
>
>
> drivers/net/wireless/ath/ath10k/core.h  |  41 +++++++++++
>  drivers/net/wireless/ath/ath10k/debug.c | 124 ++++++++++++++++++++++++++++++++
>  drivers/net/wireless/ath/ath10k/debug.h |  25 +++++++
>  drivers/net/wireless/ath/ath10k/pci.c   |  86 +++++++++++++++++++++-
>  4 files changed, 273 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/net/wireless/ath/ath10k/core.h b/drivers/net/wireless/ath/ath10k/core.h
> index 68ceef6..4068910 100644
> --- a/drivers/net/wireless/ath/ath10k/core.h
> +++ b/drivers/net/wireless/ath/ath10k/core.h
> @@ -41,6 +41,8 @@
>  #define ATH10K_FLUSH_TIMEOUT_HZ (5*HZ)
>  #define ATH10K_NUM_CHANS 38
>
> +#define REG_DUMP_COUNT_QCA988X 60 /* from pci.h */

I don't like this.


[...]
> +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 {
> +       u32 len;
> +       u32 magic; /* 0x01020304, tells us byte-order of host if we care */
> +       struct ath10k_tlv_dump_data data; /* more may follow */
> +} __packed;
> +
> +/* This will store at least the last 128 entries. */
> +#define ATH10K_DBGLOG_DATA_LEN (128 * 7 * 4)

Where does the 7 and 4 come from? Can't we use sizeof() to compute this?

The 128 should probably be a separate #define?

[...]
> +static struct ath10k_dump_file_data *ath10k_build_dump_file(struct ath10k *ar)
> +{
> +       unsigned int len = (sizeof(ar->dbglog_entry_data)
> +                           + 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);

You call this with conf_mutex held so it's a good idea to have a
lockdep here, isn't it?


[...]
> +       /* 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)

3 newlines?


> +{
> +       struct ath10k *ar = inode->i_private;
> +       int ret;
> +       struct ath10k_dump_file_data *dump;
> +
> +       if (!ar)
> +               return -EINVAL;
> +
> +       mutex_lock(&ar->conf_mutex);
> +
> +       dump = ath10k_build_dump_file(ar);
> +

Maybe it's just me, but this newline bugs me :)


> +       if (!dump) {
> +               ret = -ENODATA;
> +               goto out;
> +       }
> +
> +       file->private_data = dump;
> +       ar->crashed_since_read = false;
> +       ret = 0;
> +
> +out:
> +       mutex_unlock(&ar->conf_mutex);
> +       return ret;
> +}
> +
> +
> +static ssize_t ath10k_fw_error_dump_read(struct file *file,

2 newlines?


[...]
>  static int ath10k_debug_htt_stats_req(struct ath10k *ar)
>  {
>         u64 cookie;
> @@ -861,6 +981,9 @@ int ath10k_debug_create(struct ath10k *ar)
>         debugfs_create_file("simulate_fw_crash", S_IRUSR, ar->debug.debugfs_phy,
>                             ar, &fops_simulate_fw_crash);
>
> +       debugfs_create_file("fw_error_dump", S_IRUSR, ar->debug.debugfs_phy,
> +                           ar, &fops_fw_error_dump);
> +
>         debugfs_create_file("chip_id", S_IRUSR, ar->debug.debugfs_phy,
>                             ar, &fops_chip_id);
>
> @@ -931,4 +1054,5 @@ void ath10k_dbg_dump(enum ath10k_debug_mask mask,
>  }
>  EXPORT_SYMBOL(ath10k_dbg_dump);
>
> +

What is this newline doing here?


>  #endif /* CONFIG_ATH10K_DEBUG */
> diff --git a/drivers/net/wireless/ath/ath10k/debug.h b/drivers/net/wireless/ath/ath10k/debug.h
> index a582499..d9629f9 100644
> --- 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"
>  #include "trace.h"

Not really sure if we should be mixing pci in debug?

This actually gets me thinking we could have some stuff in hw.h that
isn't really related to the transport. I can imagine usb transport
would have pretty much the same hardware at the other side.


>  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 dbglog_buf_s {
> +       u32 next; /* pointer to dblog_buf_s. */
> +       u32 buffer; /* pointer to u8 buffer */
> +       u32 bufsize;
> +       u32 length;
> +       u32 count;
> +       u32 free;
> +} __packed;
> +
> +struct dbglog_hdr_s {
> +       u32 dbuf; /* pointer to dbglog_buf_s */
> +       u32 dropped;
> +} __packed;

These structure names look strange. Why the _s suffix? Shouldn't these
be called simply ath10k_dbglog_buf and ath10k_dbglog_hdr? Or
ath10k_{fw/hw/dev/target}_dbglog_buf/hdr? (In case you want to have a
clearer distinction these structures aren't ath10k's per se).


[...]
> +       while (dbufp) {
> +               struct dbglog_buf_s dbuf;
> +
> +               ret = ath10k_pci_diag_read_mem(ar, dbufp,
> +                                              &dbuf, sizeof(dbuf));
> +               if (ret != 0) {
> +                       ath10k_err("could not read Debug Log Area: 0x%x\n",
> +                                  dbufp);
> +                       goto save_regs_and_restart;
> +               }
> +
> +               /* We have a buffer of data */
> +               /* TODO:  Do we need to worry about bit order on some
> +                * architectures?  This seems to work fine with
> +                * x86-64 host, at least.
> +                */

ath10k_pci_diag* performs byte-swap so you're good as long as you read
word-like data from the target. I suspect this might not work so great
if you read something like, say, a mac address which might be stored
as a raw byte stream instead.

What kind of data can we expect from the dbglog?


> +               ath10k_err("[%i] next: 0x%x buf: 0x%x sz: %i len: %i count: %i free: %i\n",
> +                          i, dbuf.next, dbuf.buffer, dbuf.bufsize, dbuf.length,
> +                          dbuf.count, dbuf.free);
> +               if (dbuf.buffer && dbuf.length) {
> +                       u8 *buffer = kmalloc(dbuf.length, GFP_ATOMIC);
> +
> +                       if (buffer) {
> +                               ret = ath10k_pci_diag_read_mem(ar, dbuf.buffer,
> +                                                              buffer,
> +                                                              dbuf.length);
> +                               if (ret != 0) {
> +                                       ath10k_err("could not read Debug Log buffer: 0x%x\n",
> +                                                  dbuf.buffer);
> +                                       kfree(buffer);
> +                                       goto save_regs_and_restart;
> +                               }
> +
> +                               ath10k_dbg_save_fw_dbg_buffer(ar, buffer,
> +                                                             dbuf.length);
> +                               kfree(buffer);
> +                       }
> +               }
> +               dbufp = dbuf.next;
> +               if (dbufp == dbg_hdr.dbuf) {
> +                       /* It is a circular buffer it seems, bail if next
> +                        * is head
> +                        */

Hmm, we seem to be mixing comment styles in ath10k now I guess (this
applies to other instances of multi-line comments in your patch). What
multi-line comment style should we really be using in ath10k? Kalle?


Micha?
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ben Greear June 4, 2014, 4:48 p.m. UTC | #2
On 06/04/2014 02:04 AM, Michal Kazior wrote:
> On 3 June 2014 18:25,  <greearb@candelatech.com> wrote:
>> From: Ben Greear <greearb@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@candelatech.com>
>> ---
>>
>> This series is compile-tested only at this point.  Hoping
>> for feedback on general approach at least.
>>
>>
>> drivers/net/wireless/ath/ath10k/core.h  |  41 +++++++++++
>>  drivers/net/wireless/ath/ath10k/debug.c | 124 ++++++++++++++++++++++++++++++++
>>  drivers/net/wireless/ath/ath10k/debug.h |  25 +++++++
>>  drivers/net/wireless/ath/ath10k/pci.c   |  86 +++++++++++++++++++++-
>>  4 files changed, 273 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/net/wireless/ath/ath10k/core.h b/drivers/net/wireless/ath/ath10k/core.h
>> index 68ceef6..4068910 100644
>> --- a/drivers/net/wireless/ath/ath10k/core.h
>> +++ b/drivers/net/wireless/ath/ath10k/core.h
>> @@ -41,6 +41,8 @@
>>  #define ATH10K_FLUSH_TIMEOUT_HZ (5*HZ)
>>  #define ATH10K_NUM_CHANS 38
>>
>> +#define REG_DUMP_COUNT_QCA988X 60 /* from pci.h */
> 
> I don't like this.

You want me to make a different define for this that duplicates
the '60' value?  At least with my method above, we should get compile
errors if the values ever diverge in the two files.

>> +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 {
>> +       u32 len;
>> +       u32 magic; /* 0x01020304, tells us byte-order of host if we care */
>> +       struct ath10k_tlv_dump_data data; /* more may follow */
>> +} __packed;
>> +
>> +/* This will store at least the last 128 entries. */
>> +#define ATH10K_DBGLOG_DATA_LEN (128 * 7 * 4)
> 
> Where does the 7 and 4 come from? Can't we use sizeof() to compute this?

It is from the guts of how the firmware does debug logs.

Each entry is a max of 7 32-bit integers in length.

> The 128 should probably be a separate #define?

I don't see why...dbglog messages are variable number of 32-bit
integers in length, so the 128 is fairly arbitrary.

>> +static struct ath10k_dump_file_data *ath10k_build_dump_file(struct ath10k *ar)
>> +{
>> +       unsigned int len = (sizeof(ar->dbglog_entry_data)
>> +                           + 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);
> 
> You call this with conf_mutex held so it's a good idea to have a
> lockdep here, isn't it?

ok.

>> --- 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"
>>  #include "trace.h"
> 
> Not really sure if we should be mixing pci in debug?
> 
> This actually gets me thinking we could have some stuff in hw.h that
> isn't really related to the transport. I can imagine usb transport
> would have pretty much the same hardware at the other side.

I don't see much benefit to the separate modules for ath10k anyway,
seems like it's currently a lot of overhead for no advantage.  Maybe
with a usb NIC is released it will make more sense.

>>  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 dbglog_buf_s {
>> +       u32 next; /* pointer to dblog_buf_s. */
>> +       u32 buffer; /* pointer to u8 buffer */
>> +       u32 bufsize;
>> +       u32 length;
>> +       u32 count;
>> +       u32 free;
>> +} __packed;
>> +
>> +struct dbglog_hdr_s {
>> +       u32 dbuf; /* pointer to dbglog_buf_s */
>> +       u32 dropped;
>> +} __packed;
> 
> These structure names look strange. Why the _s suffix? Shouldn't these
> be called simply ath10k_dbglog_buf and ath10k_dbglog_hdr? Or
> ath10k_{fw/hw/dev/target}_dbglog_buf/hdr? (In case you want to have a
> clearer distinction these structures aren't ath10k's per se).

I can rename them, don't recall why I named them thus (similar patch has
been in my tree since last fall.)

> [...]
>> +       while (dbufp) {
>> +               struct dbglog_buf_s dbuf;
>> +
>> +               ret = ath10k_pci_diag_read_mem(ar, dbufp,
>> +                                              &dbuf, sizeof(dbuf));
>> +               if (ret != 0) {
>> +                       ath10k_err("could not read Debug Log Area: 0x%x\n",
>> +                                  dbufp);
>> +                       goto save_regs_and_restart;
>> +               }
>> +
>> +               /* We have a buffer of data */
>> +               /* TODO:  Do we need to worry about bit order on some
>> +                * architectures?  This seems to work fine with
>> +                * x86-64 host, at least.
>> +                */
> 
> ath10k_pci_diag* performs byte-swap so you're good as long as you read
> word-like data from the target. I suspect this might not work so great
> if you read something like, say, a mac address which might be stored
> as a raw byte stream instead.
> 
> What kind of data can we expect from the dbglog?

Any and all, it is up to user-space to decode because it requires
intimate knowledge of the firmware.  I think you might have seen
the tool I gave to QCA that decodes dbglog messages printed in ascii-hex
in kernel logs?  Same tool with tweak can decode this binary info.

Plz ask me off list if you want the tool and do not have it...I can
try to get it forwarded to you trough my QCA contacts.  The debug tool
cannot be made public w/out QCA's explicit consent, and no idea if
that would ever happen...but at least firmware devs under NDA can use it
or something similar...


Thanks,
Ben
Ben Greear June 4, 2014, 5:11 p.m. UTC | #3
On 06/04/2014 02:04 AM, Michal Kazior wrote:

>> +               if (dbufp == dbg_hdr.dbuf) {
>> +                       /* It is a circular buffer it seems, bail if next
>> +                        * is head
>> +                        */
> 
> Hmm, we seem to be mixing comment styles in ath10k now I guess (this
> applies to other instances of multi-line comments in your patch). What
> multi-line comment style should we really be using in ath10k? Kalle?

Check-patch bitches if you do it differently than what is above, so I say
we keep it as I have it.  Not worth cleaning up other comments to match
unless someone actually cares enough to do it...

Thanks,
Ben
Kalle Valo June 5, 2014, 11:29 a.m. UTC | #4
Ben Greear <greearb@candelatech.com> writes:

> On 06/04/2014 02:04 AM, Michal Kazior wrote:
>> On 3 June 2014 18:25,  <greearb@candelatech.com> wrote:
>>
>>> --- a/drivers/net/wireless/ath/ath10k/core.h
>>> +++ b/drivers/net/wireless/ath/ath10k/core.h
>>> @@ -41,6 +41,8 @@
>>>  #define ATH10K_FLUSH_TIMEOUT_HZ (5*HZ)
>>>  #define ATH10K_NUM_CHANS 38
>>>
>>> +#define REG_DUMP_COUNT_QCA988X 60 /* from pci.h */
>> 
>> I don't like this.

Me neither.

> You want me to make a different define for this that duplicates
> the '60' value?  At least with my method above, we should get compile
> errors if the values ever diverge in the two files.

There should be only one define. Somewhere (forgot where) Michal
suggested hw.h, I think this define would be a good candidate to move
there too.

>>> +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 {
>>> +       u32 len;
>>> +       u32 magic; /* 0x01020304, tells us byte-order of host if we care */
>>> +       struct ath10k_tlv_dump_data data; /* more may follow */
>>> +} __packed;
>>> +
>>> +/* This will store at least the last 128 entries. */
>>> +#define ATH10K_DBGLOG_DATA_LEN (128 * 7 * 4)
>> 
>> Where does the 7 and 4 come from? Can't we use sizeof() to compute this?
>
> It is from the guts of how the firmware does debug logs.
>
> Each entry is a max of 7 32-bit integers in length.
>
>> The 128 should probably be a separate #define?
>
> I don't see why...

To make the code more readable.

> dbglog messages are variable number of 32-bit integers in length, so
> the 128 is fairly arbitrary.

A person should immeaditely understand where the numbers are coming from
and not start googling about it. The minimum is to put your description
above to the comment. And it would be clearer to replace 4 with
sizeof(u32).
Kalle Valo June 5, 2014, 11:33 a.m. UTC | #5
Michal Kazior <michal.kazior@tieto.com> writes:

>> +               dbufp = dbuf.next;
>> +               if (dbufp == dbg_hdr.dbuf) {
>> +                       /* It is a circular buffer it seems, bail if next
>> +                        * is head
>> +                        */
>
> Hmm, we seem to be mixing comment styles in ath10k now I guess (this
> applies to other instances of multi-line comments in your patch). What
> multi-line comment style should we really be using in ath10k? Kalle?

Yeah, checkpatch is really annoying here and started to use that
different style of multiline comments. I just disabled that check on my
own setup. Personally I don't really care, but I guess we should switch
to use this "new" style.
Ben Greear June 5, 2014, 3:49 p.m. UTC | #6
On 06/05/2014 04:29 AM, Kalle Valo wrote:
> Ben Greear <greearb@candelatech.com> writes:
> 
>> On 06/04/2014 02:04 AM, Michal Kazior wrote:
>>> On 3 June 2014 18:25,  <greearb@candelatech.com> wrote:
>>>
>>>> --- a/drivers/net/wireless/ath/ath10k/core.h
>>>> +++ b/drivers/net/wireless/ath/ath10k/core.h
>>>> @@ -41,6 +41,8 @@
>>>>  #define ATH10K_FLUSH_TIMEOUT_HZ (5*HZ)
>>>>  #define ATH10K_NUM_CHANS 38
>>>>
>>>> +#define REG_DUMP_COUNT_QCA988X 60 /* from pci.h */
>>>
>>> I don't like this.
> 
> Me neither.
> 
>> You want me to make a different define for this that duplicates
>> the '60' value?  At least with my method above, we should get compile
>> errors if the values ever diverge in the two files.
> 
> There should be only one define. Somewhere (forgot where) Michal
> suggested hw.h, I think this define would be a good candidate to move
> there too.

Ok, I'll move it to hw.h

>>>> +/* This will store at least the last 128 entries. */
>>>> +#define ATH10K_DBGLOG_DATA_LEN (128 * 7 * 4)
>>>
>>> Where does the 7 and 4 come from? Can't we use sizeof() to compute this?
>>
>> It is from the guts of how the firmware does debug logs.
>>
>> Each entry is a max of 7 32-bit integers in length.
>>
>>> The 128 should probably be a separate #define?
>>
>> I don't see why...
> 
> To make the code more readable.
> 
>> dbglog messages are variable number of 32-bit integers in length, so
>> the 128 is fairly arbitrary.
> 
> A person should immeaditely understand where the numbers are coming from
> and not start googling about it. The minimum is to put your description
> above to the comment. And it would be clearer to replace 4 with
> sizeof(u32).

Would the comments I put in the PATCH 1/4 combined with the sizeof(4)
be sufficient, or do you want actual defines?  To really understand
this code you need details from how the firmware encodes the messages.
I would rather a QCA person add that info just in case it is considered
protected info...

Thanks,
Ben
Kalle Valo June 6, 2014, 6:13 a.m. UTC | #7
Ben Greear <greearb@candelatech.com> writes:

>>>>> +/* This will store at least the last 128 entries. */
>>>>> +#define ATH10K_DBGLOG_DATA_LEN (128 * 7 * 4)
>>>>
>>>> Where does the 7 and 4 come from? Can't we use sizeof() to compute this?
>>>
>>> It is from the guts of how the firmware does debug logs.
>>>
>>> Each entry is a max of 7 32-bit integers in length.
>>>
>>>> The 128 should probably be a separate #define?
>>>
>>> I don't see why...
>> 
>> To make the code more readable.
>> 
>>> dbglog messages are variable number of 32-bit integers in length, so
>>> the 128 is fairly arbitrary.
>> 
>> A person should immeaditely understand where the numbers are coming from
>> and not start googling about it. The minimum is to put your description
>> above to the comment. And it would be clearer to replace 4 with
>> sizeof(u32).
>
> Would the comments I put in the PATCH 1/4 combined with the sizeof(4)
> be sufficient, or do you want actual defines?

What you wrote about should be enough, I think you can skip the defines
for now. Something like:

        /* Each entry is a max of 7 32-bit integers in length, let's choose
         * arbitrarily 128 entries */
diff mbox

Patch

diff --git a/drivers/net/wireless/ath/ath10k/core.h b/drivers/net/wireless/ath/ath10k/core.h
index 68ceef6..4068910 100644
--- a/drivers/net/wireless/ath/ath10k/core.h
+++ b/drivers/net/wireless/ath/ath10k/core.h
@@ -41,6 +41,8 @@ 
 #define ATH10K_FLUSH_TIMEOUT_HZ (5*HZ)
 #define ATH10K_NUM_CHANS 38
 
+#define REG_DUMP_COUNT_QCA988X 60 /* from pci.h */
+
 /* Antenna noise floor */
 #define ATH10K_DEFAULT_NOISE_FLOOR -95
 
@@ -338,6 +340,39 @@  enum ath10k_dev_flags {
 	ATH10K_FLAG_CORE_REGISTERED,
 };
 
+/**
+ * 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,
+};
+
+
+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 {
+	u32 len;
+	u32 magic; /* 0x01020304, tells us byte-order of host if we care */
+	struct ath10k_tlv_dump_data data; /* more may follow */
+} __packed;
+
+/* This will store at least the last 128 entries. */
+#define ATH10K_DBGLOG_DATA_LEN (128 * 7 * 4)
+struct ath10k_dbglog_entry_storage {
+	u32 next_idx; /* Where to write next chunk of data */
+	u8 data[ATH10K_DBGLOG_DATA_LEN];
+};
+
+
 struct ath10k {
 	struct ath_common ath_common;
 	struct ieee80211_hw *hw;
@@ -488,6 +523,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];
+
 #ifdef CONFIG_ATH10K_DEBUGFS
 	struct ath10k_debug debug;
 #endif
diff --git a/drivers/net/wireless/ath/ath10k/debug.c b/drivers/net/wireless/ath/ath10k/debug.c
index 1b7ff4b..1f18412 100644
--- a/drivers/net/wireless/ath/ath10k/debug.c
+++ b/drivers/net/wireless/ath/ath10k/debug.c
@@ -577,6 +577,126 @@  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;
+
+	for (i = 0; i < len; i++) {
+		ar->dbglog_entry_data.data[z] = buffer[i];
+		z++;
+		if (z >= ATH10K_DBGLOG_DATA_LEN)
+			z = 0;
+	}
+	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)
+			    + 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);
+
+	len += hdr_len;
+	sofar += hdr_len;
+
+	/* So we can add headers to the data dump */
+	len += 2 * sizeof(*dump_tlv);
+
+	/* 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;
+
+	dump_data = (struct ath10k_dump_file_data *)(buf);
+	dump_data->len = len;
+	dump_data->magic = 0x01020304;
+
+	/* 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;
+
+	mutex_lock(&ar->conf_mutex);
+
+	dump = ath10k_build_dump_file(ar);
+
+	if (!dump) {
+		ret = -ENODATA;
+		goto out;
+	}
+
+	file->private_data = dump;
+	ar->crashed_since_read = false;
+	ret = 0;
+
+out:
+	mutex_unlock(&ar->conf_mutex);
+	return ret;
+}
+
+
+static ssize_t ath10k_fw_error_dump_read(struct file *file,
+					 char __user *user_buf,
+					 size_t count, loff_t *ppos)
+{
+	struct ath10k_dump_file_data *dump_file = file->private_data;
+
+	return simple_read_from_buffer(user_buf, count, ppos,
+				       dump_file,
+				       dump_file->len);
+}
+
+static int ath10k_fw_error_dump_release(struct inode *inode,
+					struct file *file)
+{
+	vfree(file->private_data);
+
+	return 0;
+}
+
+static const struct file_operations fops_fw_error_dump = {
+	.open = ath10k_fw_error_dump_open,
+	.read = ath10k_fw_error_dump_read,
+	.release = ath10k_fw_error_dump_release,
+	.owner = THIS_MODULE,
+	.llseek = default_llseek,
+};
+
 static int ath10k_debug_htt_stats_req(struct ath10k *ar)
 {
 	u64 cookie;
@@ -861,6 +981,9 @@  int ath10k_debug_create(struct ath10k *ar)
 	debugfs_create_file("simulate_fw_crash", S_IRUSR, ar->debug.debugfs_phy,
 			    ar, &fops_simulate_fw_crash);
 
+	debugfs_create_file("fw_error_dump", S_IRUSR, ar->debug.debugfs_phy,
+			    ar, &fops_fw_error_dump);
+
 	debugfs_create_file("chip_id", S_IRUSR, ar->debug.debugfs_phy,
 			    ar, &fops_chip_id);
 
@@ -931,4 +1054,5 @@  void ath10k_dbg_dump(enum ath10k_debug_mask mask,
 }
 EXPORT_SYMBOL(ath10k_dbg_dump);
 
+
 #endif /* CONFIG_ATH10K_DEBUG */
diff --git a/drivers/net/wireless/ath/ath10k/debug.h b/drivers/net/wireless/ath/ath10k/debug.h
index a582499..d9629f9 100644
--- 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"
 #include "trace.h"
 
 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 dbglog_buf_s {
+	u32 next; /* pointer to dblog_buf_s. */
+	u32 buffer; /* pointer to u8 buffer */
+	u32 bufsize;
+	u32 length;
+	u32 count;
+	u32 free;
+} __packed;
+
+struct dbglog_hdr_s {
+	u32 dbuf; /* pointer to dbglog_buf_s */
+	u32 dropped;
+} __packed;
+
+void ath10k_dbg_save_fw_dbg_buffer(struct ath10k *ar, u8 *buffer, int len);
+
 #endif /* _DEBUG_H_ */
diff --git a/drivers/net/wireless/ath/ath10k/pci.c b/drivers/net/wireless/ath/ath10k/pci.c
index d0004d5..36da9a5 100644
--- 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>
+#include <linux/ctype.h>
 
 #include "core.h"
 #include "debug.h"
@@ -840,6 +840,8 @@  static void ath10k_pci_hif_dump_area(struct ath10k *ar)
 	u32 host_addr;
 	int ret;
 	u32 i;
+	struct dbglog_hdr_s dbg_hdr;
+	u32 dbufp; /* pointer in target memory space */
 
 	ath10k_err("firmware crashed!\n");
 	ath10k_err("hardware name %s version 0x%x\n",
@@ -851,7 +853,7 @@  static void ath10k_pci_hif_dump_area(struct ath10k *ar)
 				       &reg_dump_area, sizeof(u32));
 	if (ret) {
 		ath10k_err("failed to read FW dump area address: %d\n", ret);
-		return;
+		goto do_restart;
 	}
 
 	ath10k_err("target register Dump Location: 0x%08X\n", reg_dump_area);
@@ -861,7 +863,7 @@  static void ath10k_pci_hif_dump_area(struct ath10k *ar)
 				       REG_DUMP_COUNT_QCA988X * sizeof(u32));
 	if (ret != 0) {
 		ath10k_err("failed to read FW dump area: %d\n", ret);
-		return;
+		goto do_restart;
 	}
 
 	BUILD_BUG_ON(REG_DUMP_COUNT_QCA988X % 4);
@@ -875,6 +877,84 @@  static void ath10k_pci_hif_dump_area(struct ath10k *ar)
 			   reg_dump_values[i + 2],
 			   reg_dump_values[i + 3]);
 
+	/* Dump the debug logs on the target */
+	host_addr = host_interest_item_address(HI_ITEM(hi_dbglog_hdr));
+	if (ath10k_pci_diag_read_mem(ar, host_addr,
+				     &reg_dump_area, sizeof(u32)) != 0) {
+		ath10k_warn("could not read hi_dbglog_hdr\n");
+		goto save_regs_and_restart;
+	}
+
+	ath10k_err("target register Debug Log Location: 0x%08X\n",
+		   reg_dump_area);
+
+	ret = ath10k_pci_diag_read_mem(ar, reg_dump_area,
+				       &dbg_hdr, sizeof(dbg_hdr));
+	if (ret != 0) {
+		ath10k_err("could not dump Debug Log Area\n");
+		goto save_regs_and_restart;
+	}
+
+	ath10k_err("Debug Log Header, dbuf: 0x%x  dropped: %i\n",
+		   dbg_hdr.dbuf, dbg_hdr.dropped);
+	dbufp = dbg_hdr.dbuf;
+	i = 0;
+	while (dbufp) {
+		struct dbglog_buf_s dbuf;
+
+		ret = ath10k_pci_diag_read_mem(ar, dbufp,
+					       &dbuf, sizeof(dbuf));
+		if (ret != 0) {
+			ath10k_err("could not read Debug Log Area: 0x%x\n",
+				   dbufp);
+			goto save_regs_and_restart;
+		}
+
+		/* We have a buffer of data */
+		/* TODO:  Do we need to worry about bit order on some
+		 * architectures?  This seems to work fine with
+		 * x86-64 host, at least.
+		 */
+		ath10k_err("[%i] next: 0x%x buf: 0x%x sz: %i len: %i count: %i free: %i\n",
+			   i, dbuf.next, dbuf.buffer, dbuf.bufsize, dbuf.length,
+			   dbuf.count, dbuf.free);
+		if (dbuf.buffer && dbuf.length) {
+			u8 *buffer = kmalloc(dbuf.length, GFP_ATOMIC);
+
+			if (buffer) {
+				ret = ath10k_pci_diag_read_mem(ar, dbuf.buffer,
+							       buffer,
+							       dbuf.length);
+				if (ret != 0) {
+					ath10k_err("could not read Debug Log buffer: 0x%x\n",
+						   dbuf.buffer);
+					kfree(buffer);
+					goto save_regs_and_restart;
+				}
+
+				ath10k_dbg_save_fw_dbg_buffer(ar, buffer,
+							      dbuf.length);
+				kfree(buffer);
+			}
+		}
+		dbufp = dbuf.next;
+		if (dbufp == dbg_hdr.dbuf) {
+			/* It is a circular buffer it seems, bail if next
+			 * is head
+			 */
+			break;
+		}
+		i++;
+	} /* While we have a debug buffer to read */
+
+save_regs_and_restart:
+	if (!ar->crashed_since_read) {
+		ar->crashed_since_read = true;
+		memcpy(ar->reg_dump_values, reg_dump_values,
+		       sizeof(ar->reg_dump_values));
+	}
+
+do_restart:
 	queue_work(ar->workqueue, &ar->restart_work);
 }