blob: 6e0082d1bda86bd7b3d4e442f6bc813d6c9d4666 [file] [log] [blame]
/*
* Copyright (c) 2012 Broadcom Corporation
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "debug.h"
#include "brcmu_utils.h"
#include "brcmu_wifi.h"
#include "bus.h"
#include "core.h"
#include "device.h"
#include "linuxisms.h"
#include "fweh.h"
#define BRCMF_HEXDUMP_WIDTH 16
// Debug level configuration. See debug.h for bits
int brcmf_msg_filter;
static zx_handle_t root_folder;
bool __brcm_dbg_err_flag = false;
void __brcm_dbg_set_err() { __brcm_dbg_err_flag = true; }
bool brcm_dbg_has_err() { return __brcm_dbg_err_flag; }
void brcm_dbg_clear_err() { __brcm_dbg_err_flag = false; }
#if CONFIG_BRCMFMAC_DBG
void __brcmf_dbg(uint32_t filter, const char* func, const char* fmt, ...) {
if (brcmf_msg_filter & filter) {
char msg[512]; // Same value hard-coded throughout devhost.c
va_list args;
va_start(args, fmt);
int n_printed = vsnprintf(msg, 512, fmt, args);
va_end(args);
if (n_printed < 0) {
snprintf(msg, 512, "(Formatting error from string '%s')", fmt);
} else if (n_printed > 0 && msg[n_printed - 1] == '\n') {
msg[--n_printed] = 0;
}
zxlogf(INFO, "brcmfmac (%s): '%s'\n", func, msg);
}
}
#else
void __brcmf_dbg(uint32_t filter, const char* fucn, const char* fmt, ...) {}
#endif // CONFIG_BRCMFMAC_DBG
void __brcmf_err(const char* func, const char* fmt, ...) {
char msg[512]; // Same value hard-coded throughout devhost.c
va_list args;
va_start(args, fmt);
int n_printed = vsnprintf(msg, 512, fmt, args);
va_end(args);
if (n_printed < 0) {
snprintf(msg, 512, "(Formatting error from string '%s')", fmt);
}
__brcm_dbg_set_err();
zxlogf(ERROR, "brcmfmac (%s): %s", func, msg);
}
void brcmf_hexdump(const void* buf, size_t len) {
if (len > 4096) {
zxlogf(INFO, "brcmfmac: Truncating hexdump to 4096 bytes\n");
len = 4096;
}
if (len == 0) {
zxlogf(INFO, "brcmfmac: Empty hexdump %p\n", buf);
return;
}
char output[64 + BRCMF_HEXDUMP_WIDTH * 3];
uint8_t* bytes = (uint8_t*)buf;
size_t i;
char* next = output;
for (i = 0; i < len; i++) {
next += sprintf(next, "%02x ", bytes[i % BRCMF_HEXDUMP_WIDTH]);
if ((i % BRCMF_HEXDUMP_WIDTH) == (BRCMF_HEXDUMP_WIDTH - 1)) {
// For larger dumps over serial, a delay may need to be added
zxlogf(INFO, "%p: %s\n", bytes, output);
next = output;
bytes += BRCMF_HEXDUMP_WIDTH;
}
}
if ((i % BRCMF_HEXDUMP_WIDTH) != 0) {
zxlogf(INFO, "%p: %s\n", bytes, output);
}
}
void brcmf_alphadump(const void* buf, size_t len) {
if (len == 0) {
brcmf_dbg(INFO, "Empty alphadump %p", buf);
return;
}
char output[140];
uint8_t* bytes = (uint8_t*)buf;
size_t i;
int nonprinting = 0;
char* next = output;
bool overflow = false;
next += sprintf(next, "Alpha: \"");
for (i = 0; i < len; i++) {
if (bytes[i] >= 32 && bytes[i] < 128) {
if (nonprinting) {
next += sprintf(next, ",%d.", nonprinting);
nonprinting = 0;
}
next += sprintf(next, "%c", bytes[i]);
} else {
nonprinting++;
}
if (next > output + 125) {
overflow = true;
break;
}
}
if (nonprinting) {
next += sprintf(next, ",%d.", nonprinting);
nonprinting = 0;
}
if (overflow) {
next += sprintf(next, ">etc<");
}
sprintf(next, "\"\n");
brcmf_dbg(INFO, "%s", output);
}
zx_status_t brcmf_debug_create_memdump(struct brcmf_bus* bus, const void* data, size_t len) {
void* dump;
size_t ramsize;
zx_status_t err;
ramsize = brcmf_bus_get_ramsize(bus);
if (!ramsize) {
return ZX_ERR_NOT_SUPPORTED;
}
dump = calloc(1, len + ramsize);
if (!dump) {
return ZX_ERR_NO_MEMORY;
}
memcpy(dump, data, len);
err = brcmf_bus_get_memdump(bus, dump + len, ramsize);
if (err != ZX_OK) {
free(dump);
return err;
}
dev_coredumpv(bus->dev, dump, len + ramsize);
return ZX_OK;
}
void brcmf_debugfs_init(void) {
zx_status_t err;
err = brcmf_debugfs_create_directory(KBUILD_MODNAME, ZX_HANDLE_INVALID, &root_folder);
if (err != ZX_OK) {
root_folder = ZX_HANDLE_INVALID;
}
}
void brcmf_debugfs_exit(void) {
if (root_folder == ZX_HANDLE_INVALID) {
return;
}
brcmf_debugfs_rm_recursive(root_folder);
root_folder = ZX_HANDLE_INVALID;
}
zx_status_t brcmf_debug_attach(struct brcmf_pub* drvr) {
struct brcmf_device* dev = drvr->bus_if->dev;
zx_status_t ret;
if (root_folder == ZX_HANDLE_INVALID) {
return ZX_ERR_NOT_FILE;
}
ret = brcmf_debugfs_create_directory(device_get_name(dev->zxdev), root_folder,
&drvr->dbgfs_dir);
return ret;
}
void brcmf_debug_detach(struct brcmf_pub* drvr) {
brcmf_fweh_unregister(drvr, BRCMF_E_PSM_WATCHDOG);
if (drvr->dbgfs_dir != ZX_HANDLE_INVALID) {
brcmf_debugfs_rm_recursive(drvr->dbgfs_dir);
}
}
zx_handle_t brcmf_debugfs_get_devdir(struct brcmf_pub* drvr) {
return drvr->dbgfs_dir;
}
zx_status_t brcmf_debugfs_add_entry(struct brcmf_pub* drvr, const char* fn,
zx_status_t (*read_fn)(struct seq_file* seq, void* data)) {
zx_handle_t e;
zx_status_t ret;
ret = brcmf_debugfs_create_sequential_file(drvr->bus_if->dev, fn, drvr->dbgfs_dir, read_fn, &e);
return ret;
}