brcmfmac: introduce checkdied debugfs functionality
The checkdied functionality provides useful information for analyzing firmware crashes. By exposing this information to a debugfs file users can easily provide its content in bug reports. The functionality is available only when CONFIG_BRCMDBG is selected. Reviewed-by: Pieter-Paul Giesberts <pieterpg@broadcom.com> Reviewed-by: Franky (Zhenhui) Lin <frankyl@broadcom.com> Signed-off-by: Arend van Spriel <arend@broadcom.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
parent
a89534edaa
commit
4fc0d0160d
|
@ -31,6 +31,7 @@
|
||||||
#include <linux/firmware.h>
|
#include <linux/firmware.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/bcma/bcma.h>
|
#include <linux/bcma/bcma.h>
|
||||||
|
#include <linux/debugfs.h>
|
||||||
#include <asm/unaligned.h>
|
#include <asm/unaligned.h>
|
||||||
#include <defs.h>
|
#include <defs.h>
|
||||||
#include <brcmu_wifi.h>
|
#include <brcmu_wifi.h>
|
||||||
|
@ -48,6 +49,9 @@
|
||||||
|
|
||||||
#define CBUF_LEN (128)
|
#define CBUF_LEN (128)
|
||||||
|
|
||||||
|
/* Device console log buffer state */
|
||||||
|
#define CONSOLE_BUFFER_MAX 2024
|
||||||
|
|
||||||
struct rte_log_le {
|
struct rte_log_le {
|
||||||
__le32 buf; /* Can't be pointer on (64-bit) hosts */
|
__le32 buf; /* Can't be pointer on (64-bit) hosts */
|
||||||
__le32 buf_size;
|
__le32 buf_size;
|
||||||
|
@ -281,7 +285,7 @@ struct rte_console {
|
||||||
* Shared structure between dongle and the host.
|
* Shared structure between dongle and the host.
|
||||||
* The structure contains pointers to trap or assert information.
|
* The structure contains pointers to trap or assert information.
|
||||||
*/
|
*/
|
||||||
#define SDPCM_SHARED_VERSION 0x0002
|
#define SDPCM_SHARED_VERSION 0x0003
|
||||||
#define SDPCM_SHARED_VERSION_MASK 0x00FF
|
#define SDPCM_SHARED_VERSION_MASK 0x00FF
|
||||||
#define SDPCM_SHARED_ASSERT_BUILT 0x0100
|
#define SDPCM_SHARED_ASSERT_BUILT 0x0100
|
||||||
#define SDPCM_SHARED_ASSERT 0x0200
|
#define SDPCM_SHARED_ASSERT 0x0200
|
||||||
|
@ -428,6 +432,29 @@ struct brcmf_console {
|
||||||
u8 *buf; /* Log buffer (host copy) */
|
u8 *buf; /* Log buffer (host copy) */
|
||||||
uint last; /* Last buffer read index */
|
uint last; /* Last buffer read index */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct brcmf_trap_info {
|
||||||
|
__le32 type;
|
||||||
|
__le32 epc;
|
||||||
|
__le32 cpsr;
|
||||||
|
__le32 spsr;
|
||||||
|
__le32 r0; /* a1 */
|
||||||
|
__le32 r1; /* a2 */
|
||||||
|
__le32 r2; /* a3 */
|
||||||
|
__le32 r3; /* a4 */
|
||||||
|
__le32 r4; /* v1 */
|
||||||
|
__le32 r5; /* v2 */
|
||||||
|
__le32 r6; /* v3 */
|
||||||
|
__le32 r7; /* v4 */
|
||||||
|
__le32 r8; /* v5 */
|
||||||
|
__le32 r9; /* sb/v6 */
|
||||||
|
__le32 r10; /* sl/v7 */
|
||||||
|
__le32 r11; /* fp/v8 */
|
||||||
|
__le32 r12; /* ip */
|
||||||
|
__le32 r13; /* sp */
|
||||||
|
__le32 r14; /* lr */
|
||||||
|
__le32 pc; /* r15 */
|
||||||
|
};
|
||||||
#endif /* DEBUG */
|
#endif /* DEBUG */
|
||||||
|
|
||||||
struct sdpcm_shared {
|
struct sdpcm_shared {
|
||||||
|
@ -439,6 +466,7 @@ struct sdpcm_shared {
|
||||||
u32 console_addr; /* Address of struct rte_console */
|
u32 console_addr; /* Address of struct rte_console */
|
||||||
u32 msgtrace_addr;
|
u32 msgtrace_addr;
|
||||||
u8 tag[32];
|
u8 tag[32];
|
||||||
|
u32 brpt_addr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sdpcm_shared_le {
|
struct sdpcm_shared_le {
|
||||||
|
@ -450,6 +478,7 @@ struct sdpcm_shared_le {
|
||||||
__le32 console_addr; /* Address of struct rte_console */
|
__le32 console_addr; /* Address of struct rte_console */
|
||||||
__le32 msgtrace_addr;
|
__le32 msgtrace_addr;
|
||||||
u8 tag[32];
|
u8 tag[32];
|
||||||
|
__le32 brpt_addr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -2953,13 +2982,311 @@ brcmf_sdbrcm_bus_txctl(struct device *dev, unsigned char *msg, uint msglen)
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
|
static inline bool brcmf_sdio_valid_shared_address(u32 addr)
|
||||||
|
{
|
||||||
|
return !(addr == 0 || ((~addr >> 16) & 0xffff) == (addr & 0xffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int brcmf_sdio_readshared(struct brcmf_sdio *bus,
|
||||||
|
struct sdpcm_shared *sh)
|
||||||
|
{
|
||||||
|
u32 addr;
|
||||||
|
int rv;
|
||||||
|
u32 shaddr = 0;
|
||||||
|
struct sdpcm_shared_le sh_le;
|
||||||
|
__le32 addr_le;
|
||||||
|
|
||||||
|
shaddr = bus->ramsize - 4;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read last word in socram to determine
|
||||||
|
* address of sdpcm_shared structure
|
||||||
|
*/
|
||||||
|
rv = brcmf_sdbrcm_membytes(bus, false, shaddr,
|
||||||
|
(u8 *)&addr_le, 4);
|
||||||
|
if (rv < 0)
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
addr = le32_to_cpu(addr_le);
|
||||||
|
|
||||||
|
brcmf_dbg(INFO, "sdpcm_shared address 0x%08X\n", addr);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if addr is valid.
|
||||||
|
* NVRAM length at the end of memory should have been overwritten.
|
||||||
|
*/
|
||||||
|
if (!brcmf_sdio_valid_shared_address(addr)) {
|
||||||
|
brcmf_dbg(ERROR, "invalid sdpcm_shared address 0x%08X\n",
|
||||||
|
addr);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read hndrte_shared structure */
|
||||||
|
rv = brcmf_sdbrcm_membytes(bus, false, addr, (u8 *)&sh_le,
|
||||||
|
sizeof(struct sdpcm_shared_le));
|
||||||
|
if (rv < 0)
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
/* Endianness */
|
||||||
|
sh->flags = le32_to_cpu(sh_le.flags);
|
||||||
|
sh->trap_addr = le32_to_cpu(sh_le.trap_addr);
|
||||||
|
sh->assert_exp_addr = le32_to_cpu(sh_le.assert_exp_addr);
|
||||||
|
sh->assert_file_addr = le32_to_cpu(sh_le.assert_file_addr);
|
||||||
|
sh->assert_line = le32_to_cpu(sh_le.assert_line);
|
||||||
|
sh->console_addr = le32_to_cpu(sh_le.console_addr);
|
||||||
|
sh->msgtrace_addr = le32_to_cpu(sh_le.msgtrace_addr);
|
||||||
|
|
||||||
|
if ((sh->flags & SDPCM_SHARED_VERSION_MASK) != SDPCM_SHARED_VERSION) {
|
||||||
|
brcmf_dbg(ERROR,
|
||||||
|
"sdpcm_shared version mismatch: dhd %d dongle %d\n",
|
||||||
|
SDPCM_SHARED_VERSION,
|
||||||
|
sh->flags & SDPCM_SHARED_VERSION_MASK);
|
||||||
|
return -EPROTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int brcmf_sdio_dump_console(struct brcmf_sdio *bus,
|
||||||
|
struct sdpcm_shared *sh, char __user *data,
|
||||||
|
size_t count)
|
||||||
|
{
|
||||||
|
u32 addr, console_ptr, console_size, console_index;
|
||||||
|
char *conbuf = NULL;
|
||||||
|
__le32 sh_val;
|
||||||
|
int rv;
|
||||||
|
loff_t pos = 0;
|
||||||
|
int nbytes = 0;
|
||||||
|
|
||||||
|
/* obtain console information from device memory */
|
||||||
|
addr = sh->console_addr + offsetof(struct rte_console, log_le);
|
||||||
|
rv = brcmf_sdbrcm_membytes(bus, false, addr,
|
||||||
|
(u8 *)&sh_val, sizeof(u32));
|
||||||
|
if (rv < 0)
|
||||||
|
return rv;
|
||||||
|
console_ptr = le32_to_cpu(sh_val);
|
||||||
|
|
||||||
|
addr = sh->console_addr + offsetof(struct rte_console, log_le.buf_size);
|
||||||
|
rv = brcmf_sdbrcm_membytes(bus, false, addr,
|
||||||
|
(u8 *)&sh_val, sizeof(u32));
|
||||||
|
if (rv < 0)
|
||||||
|
return rv;
|
||||||
|
console_size = le32_to_cpu(sh_val);
|
||||||
|
|
||||||
|
addr = sh->console_addr + offsetof(struct rte_console, log_le.idx);
|
||||||
|
rv = brcmf_sdbrcm_membytes(bus, false, addr,
|
||||||
|
(u8 *)&sh_val, sizeof(u32));
|
||||||
|
if (rv < 0)
|
||||||
|
return rv;
|
||||||
|
console_index = le32_to_cpu(sh_val);
|
||||||
|
|
||||||
|
/* allocate buffer for console data */
|
||||||
|
if (console_size <= CONSOLE_BUFFER_MAX)
|
||||||
|
conbuf = vzalloc(console_size+1);
|
||||||
|
|
||||||
|
if (!conbuf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* obtain the console data from device */
|
||||||
|
conbuf[console_size] = '\0';
|
||||||
|
rv = brcmf_sdbrcm_membytes(bus, false, console_ptr, (u8 *)conbuf,
|
||||||
|
console_size);
|
||||||
|
if (rv < 0)
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
rv = simple_read_from_buffer(data, count, &pos,
|
||||||
|
conbuf + console_index,
|
||||||
|
console_size - console_index);
|
||||||
|
if (rv < 0)
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
nbytes = rv;
|
||||||
|
if (console_index > 0) {
|
||||||
|
pos = 0;
|
||||||
|
rv = simple_read_from_buffer(data+nbytes, count, &pos,
|
||||||
|
conbuf, console_index - 1);
|
||||||
|
if (rv < 0)
|
||||||
|
goto done;
|
||||||
|
rv += nbytes;
|
||||||
|
}
|
||||||
|
done:
|
||||||
|
vfree(conbuf);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int brcmf_sdio_trap_info(struct brcmf_sdio *bus, struct sdpcm_shared *sh,
|
||||||
|
char __user *data, size_t count)
|
||||||
|
{
|
||||||
|
int error, res;
|
||||||
|
char buf[350];
|
||||||
|
struct brcmf_trap_info tr;
|
||||||
|
int nbytes;
|
||||||
|
loff_t pos = 0;
|
||||||
|
|
||||||
|
if ((sh->flags & SDPCM_SHARED_TRAP) == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error = brcmf_sdbrcm_membytes(bus, false, sh->trap_addr, (u8 *)&tr,
|
||||||
|
sizeof(struct brcmf_trap_info));
|
||||||
|
if (error < 0)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
nbytes = brcmf_sdio_dump_console(bus, sh, data, count);
|
||||||
|
if (nbytes < 0)
|
||||||
|
return nbytes;
|
||||||
|
|
||||||
|
res = scnprintf(buf, sizeof(buf),
|
||||||
|
"dongle trap info: type 0x%x @ epc 0x%08x\n"
|
||||||
|
" cpsr 0x%08x spsr 0x%08x sp 0x%08x\n"
|
||||||
|
" lr 0x%08x pc 0x%08x offset 0x%x\n"
|
||||||
|
" r0 0x%08x r1 0x%08x r2 0x%08x r3 0x%08x\n"
|
||||||
|
" r4 0x%08x r5 0x%08x r6 0x%08x r7 0x%08x\n",
|
||||||
|
le32_to_cpu(tr.type), le32_to_cpu(tr.epc),
|
||||||
|
le32_to_cpu(tr.cpsr), le32_to_cpu(tr.spsr),
|
||||||
|
le32_to_cpu(tr.r13), le32_to_cpu(tr.r14),
|
||||||
|
le32_to_cpu(tr.pc), le32_to_cpu(sh->trap_addr),
|
||||||
|
le32_to_cpu(tr.r0), le32_to_cpu(tr.r1),
|
||||||
|
le32_to_cpu(tr.r2), le32_to_cpu(tr.r3),
|
||||||
|
le32_to_cpu(tr.r4), le32_to_cpu(tr.r5),
|
||||||
|
le32_to_cpu(tr.r6), le32_to_cpu(tr.r7));
|
||||||
|
|
||||||
|
error = simple_read_from_buffer(data+nbytes, count, &pos, buf, res);
|
||||||
|
if (error < 0)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
nbytes += error;
|
||||||
|
return nbytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int brcmf_sdio_assert_info(struct brcmf_sdio *bus,
|
||||||
|
struct sdpcm_shared *sh, char __user *data,
|
||||||
|
size_t count)
|
||||||
|
{
|
||||||
|
int error = 0;
|
||||||
|
char buf[200];
|
||||||
|
char file[80] = "?";
|
||||||
|
char expr[80] = "<???>";
|
||||||
|
int res;
|
||||||
|
loff_t pos = 0;
|
||||||
|
|
||||||
|
if ((sh->flags & SDPCM_SHARED_ASSERT_BUILT) == 0) {
|
||||||
|
brcmf_dbg(INFO, "firmware not built with -assert\n");
|
||||||
|
return 0;
|
||||||
|
} else if ((sh->flags & SDPCM_SHARED_ASSERT) == 0) {
|
||||||
|
brcmf_dbg(INFO, "no assert in dongle\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sh->assert_file_addr != 0) {
|
||||||
|
error = brcmf_sdbrcm_membytes(bus, false, sh->assert_file_addr,
|
||||||
|
(u8 *)file, 80);
|
||||||
|
if (error < 0)
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
if (sh->assert_exp_addr != 0) {
|
||||||
|
error = brcmf_sdbrcm_membytes(bus, false, sh->assert_exp_addr,
|
||||||
|
(u8 *)expr, 80);
|
||||||
|
if (error < 0)
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = scnprintf(buf, sizeof(buf),
|
||||||
|
"dongle assert: %s:%d: assert(%s)\n",
|
||||||
|
file, sh->assert_line, expr);
|
||||||
|
return simple_read_from_buffer(data, count, &pos, buf, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int brcmf_sdbrcm_checkdied(struct brcmf_sdio *bus)
|
||||||
|
{
|
||||||
|
int error;
|
||||||
|
struct sdpcm_shared sh;
|
||||||
|
|
||||||
|
down(&bus->sdsem);
|
||||||
|
error = brcmf_sdio_readshared(bus, &sh);
|
||||||
|
up(&bus->sdsem);
|
||||||
|
|
||||||
|
if (error < 0)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
if ((sh.flags & SDPCM_SHARED_ASSERT_BUILT) == 0)
|
||||||
|
brcmf_dbg(INFO, "firmware not built with -assert\n");
|
||||||
|
else if (sh.flags & SDPCM_SHARED_ASSERT)
|
||||||
|
brcmf_dbg(ERROR, "assertion in dongle\n");
|
||||||
|
|
||||||
|
if (sh.flags & SDPCM_SHARED_TRAP)
|
||||||
|
brcmf_dbg(ERROR, "firmware trap in dongle\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int brcmf_sdbrcm_died_dump(struct brcmf_sdio *bus, char __user *data,
|
||||||
|
size_t count, loff_t *ppos)
|
||||||
|
{
|
||||||
|
int error = 0;
|
||||||
|
struct sdpcm_shared sh;
|
||||||
|
int nbytes = 0;
|
||||||
|
loff_t pos = *ppos;
|
||||||
|
|
||||||
|
if (pos != 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
down(&bus->sdsem);
|
||||||
|
error = brcmf_sdio_readshared(bus, &sh);
|
||||||
|
if (error < 0)
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
error = brcmf_sdio_assert_info(bus, &sh, data, count);
|
||||||
|
if (error < 0)
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
nbytes = error;
|
||||||
|
error = brcmf_sdio_trap_info(bus, &sh, data, count);
|
||||||
|
if (error < 0)
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
error += nbytes;
|
||||||
|
*ppos += error;
|
||||||
|
done:
|
||||||
|
up(&bus->sdsem);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t brcmf_sdio_forensic_read(struct file *f, char __user *data,
|
||||||
|
size_t count, loff_t *ppos)
|
||||||
|
{
|
||||||
|
struct brcmf_sdio *bus = f->private_data;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
res = brcmf_sdbrcm_died_dump(bus, data, count, ppos);
|
||||||
|
if (res > 0)
|
||||||
|
*ppos += res;
|
||||||
|
return (ssize_t)res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct file_operations brcmf_sdio_forensic_ops = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.open = simple_open,
|
||||||
|
.read = brcmf_sdio_forensic_read
|
||||||
|
};
|
||||||
|
|
||||||
static void brcmf_sdio_debugfs_create(struct brcmf_sdio *bus)
|
static void brcmf_sdio_debugfs_create(struct brcmf_sdio *bus)
|
||||||
{
|
{
|
||||||
struct brcmf_pub *drvr = bus->sdiodev->bus_if->drvr;
|
struct brcmf_pub *drvr = bus->sdiodev->bus_if->drvr;
|
||||||
|
struct dentry *dentry = brcmf_debugfs_get_devdir(drvr);
|
||||||
|
|
||||||
|
if (IS_ERR_OR_NULL(dentry))
|
||||||
|
return;
|
||||||
|
|
||||||
|
debugfs_create_file("forensics", S_IRUGO, dentry, bus,
|
||||||
|
&brcmf_sdio_forensic_ops);
|
||||||
brcmf_debugfs_create_sdio_count(drvr, &bus->sdcnt);
|
brcmf_debugfs_create_sdio_count(drvr, &bus->sdcnt);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
static int brcmf_sdbrcm_checkdied(struct brcmf_sdio *bus)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void brcmf_sdio_debugfs_create(struct brcmf_sdio *bus)
|
static void brcmf_sdio_debugfs_create(struct brcmf_sdio *bus)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -2991,11 +3318,13 @@ brcmf_sdbrcm_bus_rxctl(struct device *dev, unsigned char *msg, uint msglen)
|
||||||
rxlen, msglen);
|
rxlen, msglen);
|
||||||
} else if (timeleft == 0) {
|
} else if (timeleft == 0) {
|
||||||
brcmf_dbg(ERROR, "resumed on timeout\n");
|
brcmf_dbg(ERROR, "resumed on timeout\n");
|
||||||
|
brcmf_sdbrcm_checkdied(bus);
|
||||||
} else if (pending) {
|
} else if (pending) {
|
||||||
brcmf_dbg(CTL, "cancelled\n");
|
brcmf_dbg(CTL, "cancelled\n");
|
||||||
return -ERESTARTSYS;
|
return -ERESTARTSYS;
|
||||||
} else {
|
} else {
|
||||||
brcmf_dbg(CTL, "resumed for unknown reason?\n");
|
brcmf_dbg(CTL, "resumed for unknown reason?\n");
|
||||||
|
brcmf_sdbrcm_checkdied(bus);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rxlen)
|
if (rxlen)
|
||||||
|
@ -3817,6 +4146,7 @@ static void brcmf_sdbrcm_release_dongle(struct brcmf_sdio *bus)
|
||||||
static void brcmf_sdbrcm_release(struct brcmf_sdio *bus)
|
static void brcmf_sdbrcm_release(struct brcmf_sdio *bus)
|
||||||
{
|
{
|
||||||
brcmf_dbg(TRACE, "Enter\n");
|
brcmf_dbg(TRACE, "Enter\n");
|
||||||
|
|
||||||
if (bus) {
|
if (bus) {
|
||||||
/* De-register interrupt handler */
|
/* De-register interrupt handler */
|
||||||
brcmf_sdio_intr_unregister(bus->sdiodev);
|
brcmf_sdio_intr_unregister(bus->sdiodev);
|
||||||
|
|
Loading…
Reference in New Issue