On Sat, Jun 9, 2012 at 10:51 PM, Arend van Spriel <arend@xxxxxxxxxxxx> wrote: > 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@xxxxxxxxxxxx> > Reviewed-by: Franky (Zhenhui) Lin <frankyl@xxxxxxxxxxxx> > Signed-off-by: Arend van Spriel <arend@xxxxxxxxxxxx> > --- > drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c | 332 +++++++++++++++++++- > 1 file changed, 331 insertions(+), 1 deletion(-) > > diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c > index a07fb01..5a33b42 100644 > --- a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c > +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c > @@ -31,6 +31,7 @@ > #include <linux/firmware.h> > #include <linux/module.h> > #include <linux/bcma/bcma.h> > +#include <linux/debugfs.h> > #include <asm/unaligned.h> > #include <defs.h> > #include <brcmu_wifi.h> > @@ -48,6 +49,9 @@ > > #define CBUF_LEN (128) > > +/* Device console log buffer state */ > +#define CONSOLE_BUFFER_MAX 2024 Just out of curiosity; what is the significance of this number? > + > struct rte_log_le { > __le32 buf; /* Can't be pointer on (64-bit) hosts */ > __le32 buf_size; > @@ -281,7 +285,7 @@ struct rte_console { > * Shared structure between dongle and the host. > * 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_ASSERT_BUILT 0x0100 > #define SDPCM_SHARED_ASSERT 0x0200 > @@ -428,6 +432,29 @@ struct brcmf_console { > u8 *buf; /* Log buffer (host copy) */ > 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 */ > > struct sdpcm_shared { > @@ -439,6 +466,7 @@ struct sdpcm_shared { > u32 console_addr; /* Address of struct rte_console */ > u32 msgtrace_addr; > u8 tag[32]; > + u32 brpt_addr; > }; > > struct sdpcm_shared_le { > @@ -450,6 +478,7 @@ struct sdpcm_shared_le { > __le32 console_addr; /* Address of struct rte_console */ > __le32 msgtrace_addr; > u8 tag[32]; > + __le32 brpt_addr; > }; > > > @@ -2953,13 +2982,311 @@ brcmf_sdbrcm_bus_txctl(struct device *dev, unsigned char *msg, uint msglen) > } > > #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 = kzalloc(console_size+1, GFP_ATOMIC); > + > + 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: > + kfree(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) > { > 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); > } > #else > +static int brcmf_sdbrcm_checkdied(struct brcmf_sdio *bus) > +{ > + return 0; > +} > + > 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); > } else if (timeleft == 0) { > brcmf_dbg(ERROR, "resumed on timeout\n"); > + brcmf_sdbrcm_checkdied(bus); > } else if (pending) { > brcmf_dbg(CTL, "cancelled\n"); > return -ERESTARTSYS; > } else { > brcmf_dbg(CTL, "resumed for unknown reason?\n"); > + brcmf_sdbrcm_checkdied(bus); > } > > 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) > { > brcmf_dbg(TRACE, "Enter\n"); > + > if (bus) { > /* De-register interrupt handler */ > brcmf_sdio_intr_unregister(bus->sdiodev); > -- > 1.7.9.5 > > > -- > To unsubscribe from this list: send the line "unsubscribe linux-wireless" in > the body of a message to majordomo@xxxxxxxxxxxxxxx > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Vista: [V]iruses, [I]ntruders, [S]pyware, [T]rojans and [A]dware. :-) -- To unsubscribe from this list: send the line "unsubscribe linux-wireless" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html