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:
Arend van Spriel 2012-06-14 12:16:57 +02:00 committed by John W. Linville
parent a89534edaa
commit 4fc0d0160d
1 changed files with 331 additions and 1 deletions

View File

@ -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);