blob: 78f619cc6c483dc0157e96f93baceb3a65985210 [file] [log] [blame]
/*
* log_dump - debugability support for dumping logs to file
*
* Copyright (C) 2024, Broadcom.
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed to you
* under the terms of the GNU General Public License version 2 (the "GPL"),
* available at http://www.broadcom.com/licenses/GPLv2.php, with the
* following added to such license:
*
* As a special exception, the copyright holders of this software give you
* permission to link this software with independent modules, and to copy and
* distribute the resulting executable under terms of your choice, provided that
* you also meet, for each linked independent module, the terms and conditions of
* the license of that module. An independent module is a module which is not
* derived from this software. The special exception does not apply to any
* modifications of the software.
*
*
* <<Broadcom-WL-IPTag/Dual:>>
*
*/
#ifdef DHD_LOG_DUMP
#include <osl.h>
#include <bcmutils.h>
#include <bcmendian.h>
#include <bcmstdlib_s.h>
#include <dngl_stats.h>
#include <dhd_linux_priv.h>
#include <dhd_linux_wq.h>
#include <dhd.h>
#include <dhd_proto.h>
#include <dhd_log_dump.h>
#ifdef DHD_EVENT_LOG_FILTER
#include <dhd_event_log_filter.h>
#endif /* DHD_EVENT_LOG_FILTER */
#ifdef DHD_PKT_LOGGING
#include <dhd_pktlog.h>
#endif /* DHD_PKT_LOGGING */
#if defined(WL_CFG80211)
#include <wl_cfg80211.h>
#endif
extern char dhd_version[];
extern char fw_version[];
struct dhd_log_dump_buf g_dld_buf[DLD_BUFFER_NUM];
/* Only header for log dump buffers is stored in array
* header for sections like 'dhd dump', 'ext trap'
* etc, is not in the array, because they are not log
* ring buffers
*/
dld_hdr_t dld_hdrs[DLD_BUFFER_NUM] = {
{GENERAL_LOG_HDR, LOG_DUMP_SECTION_GENERAL},
{PRESERVE_LOG_HDR, LOG_DUMP_SECTION_PRESERVE},
{SPECIAL_LOG_HDR, LOG_DUMP_SECTION_SPECIAL}
};
static int dld_buf_size[DLD_BUFFER_NUM] = {
LOG_DUMP_GENERAL_MAX_BUFSIZE, /* DLD_BUF_TYPE_GENERAL */
LOG_DUMP_PRESERVE_MAX_BUFSIZE, /* DLD_BUF_TYPE_PRESERVE */
LOG_DUMP_SPECIAL_MAX_BUFSIZE, /* DLD_BUF_TYPE_SPECIAL */
};
int logdump_max_filesize = LOG_DUMP_MAX_FILESIZE;
module_param(logdump_max_filesize, int, 0644);
int logdump_max_bufsize = LOG_DUMP_GENERAL_MAX_BUFSIZE;
module_param(logdump_max_bufsize, int, 0644);
#ifdef EWP_ECNTRS_LOGGING
int logdump_ecntr_enable = TRUE;
#else
int logdump_ecntr_enable = FALSE;
#endif /* EWP_ECNTRS_LOGGING */
module_param(logdump_ecntr_enable, int, 0644);
#ifdef EWP_RTT_LOGGING
int logdump_rtt_enable = TRUE;
#else
int logdump_rtt_enable = FALSE;
#endif /* EWP_RTT_LOGGING */
int logdump_prsrv_tailsize = DHD_LOG_DUMP_MAX_TAIL_FLUSH_SIZE;
#ifdef DHD_DEBUGABILITY_DEBUG_DUMP
static dhd_debug_dump_ring_entry_t dhd_debug_dump_ring_map[] = {
{LOG_DUMP_SECTION_TIMESTAMP, DEBUG_DUMP_RING1_ID},
{LOG_DUMP_SECTION_ECNTRS, DEBUG_DUMP_RING2_ID},
{LOG_DUMP_SECTION_STATUS, DEBUG_DUMP_RING1_ID},
{LOG_DUMP_SECTION_RTT, DEBUG_DUMP_RING2_ID},
{LOG_DUMP_SECTION_PKTID_MAP_LOG, DEBUG_DUMP_RING2_ID},
{LOG_DUMP_SECTION_PKTID_UNMAP_LOG, DEBUG_DUMP_RING2_ID},
{LOG_DUMP_SECTION_DHD_DUMP, DEBUG_DUMP_RING1_ID},
{LOG_DUMP_SECTION_EXT_TRAP, DEBUG_DUMP_RING1_ID},
{LOG_DUMP_SECTION_HEALTH_CHK, DEBUG_DUMP_RING1_ID},
{LOG_DUMP_SECTION_COOKIE, DEBUG_DUMP_RING1_ID},
{LOG_DUMP_SECTION_RING, DEBUG_DUMP_RING1_ID},
};
#endif /* DHD_DEBUGABILITY_DEBUG_DUMP */
#ifdef CUSTOMER_HW4_DEBUG
static void
dhd_log_dump_print_to_kmsg(char *bufptr, unsigned long len)
{
char tmp_buf[DHD_LOG_DUMP_MAX_TEMP_BUFFER_SIZE + 1];
char *end = NULL;
unsigned long plen = 0;
if (!bufptr || !len)
return;
bzero(tmp_buf, DHD_LOG_DUMP_MAX_TEMP_BUFFER_SIZE);
end = bufptr + len;
while (bufptr < end) {
if ((bufptr + DHD_LOG_DUMP_MAX_TEMP_BUFFER_SIZE) < end) {
memcpy(tmp_buf, bufptr, DHD_LOG_DUMP_MAX_TEMP_BUFFER_SIZE);
tmp_buf[DHD_LOG_DUMP_MAX_TEMP_BUFFER_SIZE] = '\0';
printf("%s", tmp_buf);
bufptr += DHD_LOG_DUMP_MAX_TEMP_BUFFER_SIZE;
} else {
plen = (unsigned long)end - (unsigned long)bufptr;
memcpy(tmp_buf, bufptr, plen);
tmp_buf[plen] = '\0';
printf("%s", tmp_buf);
bufptr += plen;
}
}
}
static void
dhd_log_dump_print_tail(dhd_pub_t *dhdp,
struct dhd_log_dump_buf *dld_buf,
uint tail_len)
{
char *flush_ptr1 = NULL, *flush_ptr2 = NULL;
unsigned long len_flush1 = 0, len_flush2 = 0;
unsigned long flags = 0;
/* need to hold the lock before accessing 'present' and 'remain' ptrs */
DHD_LOG_DUMP_BUF_LOCK(&dld_buf->lock, flags);
flush_ptr1 = dld_buf->present - tail_len;
if (flush_ptr1 >= dld_buf->front) {
/* tail content is within the buffer */
flush_ptr2 = NULL;
len_flush1 = tail_len;
} else if (dld_buf->wraparound) {
/* tail content spans the buffer length i.e, wrap around */
flush_ptr1 = dld_buf->front;
len_flush1 = (unsigned long)dld_buf->present - (unsigned long)flush_ptr1;
len_flush2 = (unsigned long)tail_len - len_flush1;
flush_ptr2 = (char *)((unsigned long)dld_buf->max -
(unsigned long)len_flush2);
} else {
/* amt of logs in buffer is less than tail size */
flush_ptr1 = dld_buf->front;
flush_ptr2 = NULL;
len_flush1 = (unsigned long)dld_buf->present - (unsigned long)dld_buf->front;
}
DHD_LOG_DUMP_BUF_UNLOCK(&dld_buf->lock, flags);
printf("\n================= LOG_DUMP tail =================\n");
if (flush_ptr2) {
dhd_log_dump_print_to_kmsg(flush_ptr2, len_flush2);
}
dhd_log_dump_print_to_kmsg(flush_ptr1, len_flush1);
printf("\n===================================================\n");
}
#endif /* CUSTOMER_HW4_DEBUG */
int
dhd_log_flush(dhd_pub_t *dhdp, log_dump_type_t *type)
{
unsigned long flags = 0;
#ifdef EWP_EDL
int i = 0;
#endif /* EWP_EDL */
dhd_info_t *dhd_info = NULL;
BCM_REFERENCE(dhd_info);
/* if dhdp is null, its extremely unlikely that log dump will be scheduled
* so not freeing 'type' here is ok, even if we want to free 'type'
* we cannot do so, since 'dhdp->osh' is unavailable
* as dhdp is null
*/
if (!dhdp || !type) {
if (dhdp) {
DHD_GENERAL_LOCK(dhdp, flags);
DHD_BUS_BUSY_CLEAR_IN_LOGDUMP(dhdp);
dhd_os_busbusy_wake(dhdp);
DHD_GENERAL_UNLOCK(dhdp, flags);
}
return BCME_ERROR;
}
#if defined(BCMPCIE)
if (dhd_bus_get_linkdown(dhdp)) {
/* As link is down donot collect any data over PCIe.
* Also return BCME_OK to caller, so that caller can
* dump all the outstanding data to file
*/
return BCME_OK;
}
#endif /* BCMPCIE */
dhd_info = (dhd_info_t *)dhdp->info;
/* in case of trap get preserve logs from ETD */
#if defined(BCMPCIE) && defined(EWP_ETD_PRSRV_LOGS)
if (dhdp->dongle_trap_occured &&
dhdp->extended_trap_data) {
dhdpcie_get_etd_preserve_logs(dhdp, (uint8 *)dhdp->extended_trap_data,
&dhd_info->event_data);
}
#endif /* BCMPCIE */
/* flush the event work items to get any fw events/logs
* flush_work is a blocking call
*/
#ifdef SHOW_LOGTRACE
#ifdef EWP_EDL
if (dhd_info->pub.dongle_edl_support) {
/* wait till existing edl items are processed */
dhd_flush_logtrace_process(dhd_info);
/* dhd_flush_logtrace_process will ensure the work items in the ring
* (EDL ring) from rd to wr are processed. But if wr had
* wrapped around, only the work items from rd to ring-end are processed.
* So to ensure that the work items at the
* beginning of ring are also processed in the wrap around case, call
* it twice
*/
for (i = 0; i < 2; i++) {
/* blocks till the edl items are processed */
dhd_flush_logtrace_process(dhd_info);
}
} else {
dhd_flush_logtrace_process(dhd_info);
}
#else
dhd_flush_logtrace_process(dhd_info);
#endif /* EWP_EDL */
#endif /* SHOW_LOGTRACE */
#ifdef CUSTOMER_HW4_DEBUG
/* print last 'x' KB of preserve buffer data to kmsg console
* this is to address cases where debug_dump is not
* available for debugging
*/
dhd_log_dump_print_tail(dhdp,
&g_dld_buf[DLD_BUF_TYPE_PRESERVE], logdump_prsrv_tailsize);
#endif /* CUSTOMER_HW4_DEBUG */
return BCME_OK;
}
void
dhd_log_dump(void *handle, void *event_info, u8 event)
{
dhd_info_t *dhd = handle;
log_dump_type_t *type = (log_dump_type_t *)event_info;
if (!dhd || !type) {
DHD_ERROR(("%s: dhd/type is NULL\n", __FUNCTION__));
return;
}
if (dhd->pub.skip_logdmp) {
DHD_PRINT(("%s: skip_logdmp is set, return\n", __FUNCTION__));
return;
}
#ifdef WL_CFG80211
/* flush the fw preserve logs */
wl_flush_fw_log_buffer(dhd_linux_get_primary_netdev(&dhd->pub),
FW_LOGSET_MASK_ALL);
#endif
/* there are currently 3 possible contexts from which
* log dump can be scheduled -
* 1.TRAP 2.supplicant DEBUG_DUMP pvt driver command
* 3.HEALTH CHECK event
* The concise debug info buffer is a shared resource
* and in case a trap is one of the contexts then both the
* scheduled work queues need to run because trap data is
* essential for debugging. Hence a mutex lock is acquired
* before calling do_dhd_log_dump().
*/
DHD_PRINT(("%s: calling log dump.. \n", __FUNCTION__));
dhd_os_logdump_lock(&dhd->pub);
DHD_OS_WAKE_LOCK(&dhd->pub);
if (do_dhd_log_dump(&dhd->pub, type) != BCME_OK) {
DHD_ERROR(("%s: writing debug dump to the file failed\n", __FUNCTION__));
}
DHD_OS_WAKE_UNLOCK(&dhd->pub);
dhd_os_logdump_unlock(&dhd->pub);
}
void dhd_schedule_log_dump(dhd_pub_t *dhdp, void *type)
{
DHD_PRINT(("%s: scheduling log dump.. \n", __FUNCTION__));
dhd_deferred_schedule_work(dhdp->info->dhd_deferred_wq,
type, DHD_WQ_WORK_DHD_LOG_DUMP,
dhd_log_dump, DHD_WQ_WORK_PRIORITY_HIGH);
}
void
dhd_print_buf_addr(dhd_pub_t *dhdp, char *name, void *buf, unsigned int size)
{
if (((dhdp->memdump_enabled > DUMP_DISABLED) &&
(dhdp->memdump_enabled < DUMP_MEMFILE_MAX)) ||
(dhdp->memdump_type == DUMP_TYPE_SMMU_FAULT) ||
#ifdef DHD_DETECT_CONSECUTIVE_MFG_HANG
(dhdp->op_mode & DHD_FLAG_MFG_MODE &&
(dhdp->hang_count >= MAX_CONSECUTIVE_MFG_HANG_COUNT-1)) ||
#endif /* DHD_DETECT_CONSECUTIVE_MFG_HANG */
FALSE) {
#if defined(CONFIG_ARM64)
DHD_PRINT(("-------- %s: buf(va)=%llx, buf(pa)=%llx, bufsize=%d\n",
name, (uint64)buf, (uint64)__virt_to_phys((ulong)buf), size));
#elif defined(__ARM_ARCH_7A__)
DHD_PRINT(("-------- %s: buf(va)=%x, buf(pa)=%x, bufsize=%d\n",
name, (uint32)buf, (uint32)__virt_to_phys((ulong)buf), size));
#endif /* __ARM_ARCH_7A__ */
}
}
void
dhd_log_dump_buf_addr(dhd_pub_t *dhdp, log_dump_type_t *type)
{
int i;
unsigned long wr_size = 0;
struct dhd_log_dump_buf *dld_buf = &g_dld_buf[0];
size_t log_size = 0;
char buf_name[DHD_PRINT_BUF_NAME_LEN];
dhd_dbg_ring_t *ring = NULL;
BCM_REFERENCE(ring);
for (i = 0; i < DLD_BUFFER_NUM; i++) {
dld_buf = &g_dld_buf[i];
log_size = (unsigned long)dld_buf->max -
(unsigned long)dld_buf->buffer;
if (dld_buf->wraparound) {
wr_size = log_size;
} else {
wr_size = (unsigned long)dld_buf->present -
(unsigned long)dld_buf->front;
}
scnprintf(buf_name, sizeof(buf_name), "dlb_buf[%d]", i);
dhd_print_buf_addr(dhdp, buf_name, dld_buf, dld_buf_size[i]);
scnprintf(buf_name, sizeof(buf_name), "dlb_buf[%d] buffer", i);
dhd_print_buf_addr(dhdp, buf_name, dld_buf->buffer, wr_size);
scnprintf(buf_name, sizeof(buf_name), "dlb_buf[%d] present", i);
dhd_print_buf_addr(dhdp, buf_name, dld_buf->present, wr_size);
scnprintf(buf_name, sizeof(buf_name), "dlb_buf[%d] front", i);
dhd_print_buf_addr(dhdp, buf_name, dld_buf->front, wr_size);
}
#ifdef DEBUGABILITY_ECNTRS_LOGGING
/* periodic flushing of ecounters is NOT supported */
if (*type == DLD_BUF_TYPE_ALL &&
logdump_ecntr_enable &&
dhdp->ecntr_dbg_ring) {
ring = (dhd_dbg_ring_t *)dhdp->ecntr_dbg_ring;
dhd_print_buf_addr(dhdp, "ecntr_dbg_ring", ring, LOG_DUMP_ECNTRS_MAX_BUFSIZE);
dhd_print_buf_addr(dhdp, "ecntr_dbg_ring ring_buf", ring->ring_buf,
LOG_DUMP_ECNTRS_MAX_BUFSIZE);
}
#endif /* DEBUGABILITY_ECNTRS_LOGGING */
#if defined(BCMPCIE)
if (dhdp->dongle_trap_occured && dhdp->extended_trap_data) {
dhd_print_buf_addr(dhdp, "extended_trap_data", dhdp->extended_trap_data,
BCMPCIE_EXT_TRAP_DATA_MAXLEN);
}
#endif /* BCMPCIE */
#if defined(DHD_FW_COREDUMP) && defined(DNGL_EVENT_SUPPORT)
/* if health check event was received */
if (dhdp->memdump_type == DUMP_TYPE_DONGLE_HOST_EVENT) {
dhd_print_buf_addr(dhdp, "health_chk_event_data", dhdp->health_chk_event_data,
HEALTH_CHK_BUF_SIZE);
}
#endif /* DHD_FW_COREDUMP && DNGL_EVENT_SUPPORT */
/* append the concise debug information */
if (dhdp->concise_dbg_buf) {
dhd_print_buf_addr(dhdp, "concise_dbg_buf", dhdp->concise_dbg_buf,
CONCISE_DUMP_BUFLEN);
}
}
#ifdef DHD_SSSR_DUMP
#ifdef DHD_COREDUMP
extern dhd_coredump_t dhd_coredump_types[];
#endif /* DHD_COREDUMP */
int
dhdpcie_sssr_dump_get_before_after_len(dhd_pub_t *dhd, uint32 *arr_len)
{
int i = 0;
uint dig_buf_size = 0;
DHD_PRINT(("%s\n", __FUNCTION__));
/* core 0 */
i = 0;
#ifdef DHD_SSSR_DUMP_BEFORE_SR
if (dhd->sssr_d11_before[i] && dhd->sssr_d11_outofreset[i] &&
(dhd->sssr_dump_mode == SSSR_DUMP_MODE_SSSR)) {
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_CORE0_BEFORE].length =
#endif /* DHD_COREDUMP */
arr_len[SSSR_C0_D11_BEFORE] = dhd_sssr_mac_buf_size(dhd, i);
DHD_PRINT(("%s: arr_len[SSSR_C0_D11_BEFORE] : %d\n", __FUNCTION__,
arr_len[SSSR_C0_D11_BEFORE]));
#ifdef DHD_LOG_DUMP
dhd_print_buf_addr(dhd, "SSSR_C0_D11_BEFORE",
dhd->sssr_d11_before[i], arr_len[SSSR_C0_D11_BEFORE]);
#endif /* DHD_LOG_DUMP */
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_CORE0_BEFORE].bufptr =
dhd->sssr_d11_before[i];
#endif /* DHD_COREDUMP */
}
#endif /* DHD_SSSR_DUMP_BEFORE_SR */
if (dhd->sssr_d11_after[i] && dhd->sssr_d11_outofreset[i]) {
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_CORE0_AFTER].length =
#endif /* DHD_COREDUMP */
arr_len[SSSR_C0_D11_AFTER] = dhd_sssr_mac_buf_size(dhd, i);
DHD_PRINT(("%s: arr_len[SSSR_C0_D11_AFTER] : %d\n", __FUNCTION__,
arr_len[SSSR_C0_D11_AFTER]));
#ifdef DHD_LOG_DUMP
dhd_print_buf_addr(dhd, "SSSR_C0_D11_AFTER",
dhd->sssr_d11_after[i], arr_len[SSSR_C0_D11_AFTER]);
#endif /* DHD_LOG_DUMP */
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_CORE0_AFTER].bufptr =
dhd->sssr_d11_after[i];
#endif /* DHD_COREDUMP */
}
/* core 1 */
i = 1;
#ifdef DHD_SSSR_DUMP_BEFORE_SR
if (dhd->sssr_d11_before[i] && dhd->sssr_d11_outofreset[i] &&
(dhd->sssr_dump_mode == SSSR_DUMP_MODE_SSSR)) {
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_CORE1_BEFORE].length =
#endif /* DHD_COREDUMP */
arr_len[SSSR_C1_D11_BEFORE] = dhd_sssr_mac_buf_size(dhd, i);
DHD_PRINT(("%s: arr_len[SSSR_C1_D11_BEFORE] : %d\n", __FUNCTION__,
arr_len[SSSR_C1_D11_BEFORE]));
#ifdef DHD_LOG_DUMP
dhd_print_buf_addr(dhd, "SSSR_C1_D11_BEFORE",
dhd->sssr_d11_before[i], arr_len[SSSR_C1_D11_BEFORE]);
#endif /* DHD_LOG_DUMP */
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_CORE1_BEFORE].bufptr =
dhd->sssr_d11_before[i];
#endif /* DHD_COREDUMP */
}
#endif /* DHD_SSSR_DUMP_BEFORE_SR */
if (dhd->sssr_d11_after[i] && dhd->sssr_d11_outofreset[i]) {
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_CORE1_AFTER].length =
#endif /* DHD_COREDUMP */
arr_len[SSSR_C1_D11_AFTER] = dhd_sssr_mac_buf_size(dhd, i);
DHD_PRINT(("%s: arr_len[SSSR_C1_D11_AFTER] : %d\n", __FUNCTION__,
arr_len[SSSR_C1_D11_AFTER]));
#ifdef DHD_LOG_DUMP
dhd_print_buf_addr(dhd, "SSSR_C1_D11_AFTER",
dhd->sssr_d11_after[i], arr_len[SSSR_C1_D11_AFTER]);
#endif /* DHD_LOG_DUMP */
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_CORE1_AFTER].bufptr =
dhd->sssr_d11_after[i];
#endif /* DHD_COREDUMP */
}
/* core 2 scan core */
if (dhd->sssr_reg_info->rev2.version >= SSSR_REG_INFO_VER_2) {
i = 2;
#ifdef DHD_SSSR_DUMP_BEFORE_SR
if (dhd->sssr_d11_before[i] && dhd->sssr_d11_outofreset[i] &&
(dhd->sssr_dump_mode == SSSR_DUMP_MODE_SSSR)) {
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_CORE2_BEFORE].length =
#endif /* DHD_COREDUMP */
arr_len[SSSR_C2_D11_BEFORE] = dhd_sssr_mac_buf_size(dhd, i);
DHD_PRINT(("%s: arr_len[SSSR_C2_D11_BEFORE] : %d\n", __FUNCTION__,
arr_len[SSSR_C2_D11_BEFORE]));
#ifdef DHD_LOG_DUMP
dhd_print_buf_addr(dhd, "SSSR_C2_D11_BEFORE",
dhd->sssr_d11_before[i], arr_len[SSSR_C2_D11_BEFORE]);
#endif /* DHD_LOG_DUMP */
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_CORE2_BEFORE].bufptr =
dhd->sssr_d11_before[i];
#endif /* DHD_COREDUMP */
}
#endif /* DHD_SSSR_DUMP_BEFORE_SR */
if (dhd->sssr_d11_after[i] && dhd->sssr_d11_outofreset[i]) {
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_CORE2_AFTER].length =
#endif /* DHD_COREDUMP */
arr_len[SSSR_C2_D11_AFTER] = dhd_sssr_mac_buf_size(dhd, i);
DHD_PRINT(("%s: arr_len[SSSR_C2_D11_AFTER] : %d\n", __FUNCTION__,
arr_len[SSSR_C2_D11_AFTER]));
#ifdef DHD_LOG_DUMP
dhd_print_buf_addr(dhd, "SSSR_C2_D11_AFTER",
dhd->sssr_d11_after[i], arr_len[SSSR_C2_D11_AFTER]);
#endif /* DHD_LOG_DUMP */
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_CORE2_AFTER].bufptr =
dhd->sssr_d11_after[i];
#endif /* DHD_COREDUMP */
}
}
/* DIG core or VASIP */
dig_buf_size = dhd_sssr_dig_buf_size(dhd);
#ifdef DHD_SSSR_DUMP_BEFORE_SR
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_DIG_BEFORE].length =
#endif /* DHD_COREDUMP */
arr_len[SSSR_DIG_BEFORE] = (dhd->sssr_dig_buf_before) ? dig_buf_size : 0;
DHD_PRINT(("%s: arr_len[SSSR_DIG_BEFORE] : %d\n", __FUNCTION__,
arr_len[SSSR_DIG_BEFORE]));
#ifdef DHD_LOG_DUMP
if (dhd->sssr_dig_buf_before) {
dhd_print_buf_addr(dhd, "SSSR_DIG_BEFORE",
dhd->sssr_dig_buf_before, arr_len[SSSR_DIG_BEFORE]);
}
#endif /* DHD_LOG_DUMP */
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_DIG_BEFORE].bufptr =
dhd->sssr_dig_buf_before;
#endif /* DHD_COREDUMP */
#endif /* DHD_SSSR_DUMP_BEFORE_SR */
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_DIG_AFTER].length =
#endif /* DHD_COREDUMP */
arr_len[SSSR_DIG_AFTER] = (dhd->sssr_dig_buf_after) ? dig_buf_size : 0;
DHD_PRINT(("%s: arr_len[SSSR_DIG_AFTER] : %d\n", __FUNCTION__,
arr_len[SSSR_DIG_AFTER]));
#ifdef DHD_LOG_DUMP
if (dhd->sssr_dig_buf_after) {
dhd_print_buf_addr(dhd, "SSSR_DIG_AFTER",
dhd->sssr_dig_buf_after, arr_len[SSSR_DIG_AFTER]);
}
#endif /* DHD_LOG_DUMP */
#ifdef DHD_COREDUMP
dhd_coredump_types[DHD_COREDUMP_TYPE_SSSRDUMP_DIG_AFTER].bufptr =
dhd->sssr_dig_buf_after;
#endif /* DHD_COREDUMP */
return BCME_OK;
}
void
dhd_nla_put_sssr_dump_len(void *ndev, uint32 *arr_len)
{
dhd_info_t *dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)ndev);
dhd_pub_t *dhdp = &dhd_info->pub;
if (dhdp->sssr_dump_collected) {
dhdpcie_sssr_dump_get_before_after_len(dhdp, arr_len);
}
}
#endif /* DHD_SSSR_DUMP */
uint32
dhd_get_time_str_len(void)
{
char *ts = NULL, time_str[128];
ts = dhd_log_dump_get_timestamp();
snprintf(time_str, sizeof(time_str),
"\n\n ========== LOG DUMP TAKEN AT : %s =========\n", ts);
return strlen(time_str);
}
#if defined(BCMPCIE)
uint32
dhd_get_ext_trap_len(void *ndev, dhd_pub_t *dhdp)
{
int length = 0;
log_dump_section_hdr_t sec_hdr;
dhd_info_t *dhd_info;
if (ndev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)ndev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return length;
if (dhdp->extended_trap_data) {
length = (strlen(EXT_TRAP_LOG_HDR)
+ sizeof(sec_hdr) + BCMPCIE_EXT_TRAP_DATA_MAXLEN);
}
return length;
}
#endif /* BCMPCIE */
#if defined(DHD_FW_COREDUMP) && defined(DNGL_EVENT_SUPPORT)
uint32
dhd_get_health_chk_len(void *ndev, dhd_pub_t *dhdp)
{
int length = 0;
log_dump_section_hdr_t sec_hdr;
dhd_info_t *dhd_info;
if (ndev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)ndev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return length;
if (dhdp->memdump_type == DUMP_TYPE_DONGLE_HOST_EVENT) {
length = (strlen(HEALTH_CHK_LOG_HDR)
+ sizeof(sec_hdr) + HEALTH_CHK_BUF_SIZE);
}
return length;
}
#endif /* DHD_FW_COREDUMP && DNGL_EVENT_SUPPORT */
uint32
dhd_get_dhd_dump_len(void *ndev, dhd_pub_t *dhdp)
{
uint32 length = 0;
log_dump_section_hdr_t sec_hdr;
dhd_info_t *dhd_info;
int remain_len = 0;
if (ndev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)ndev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return length;
if (dhdp->concise_dbg_buf) {
remain_len = dhd_dump(dhdp, (char *)dhdp->concise_dbg_buf, CONCISE_DUMP_BUFLEN);
if (remain_len <= 0 || remain_len >= CONCISE_DUMP_BUFLEN) {
DHD_ERROR(("%s: error getting concise debug info ! remain_len: %d\n",
__FUNCTION__, remain_len));
return length;
}
length += (uint32)(CONCISE_DUMP_BUFLEN - remain_len);
}
length += (uint32)(strlen(DHD_DUMP_LOG_HDR) + sizeof(sec_hdr));
return length;
}
#ifdef EWP_RTT_LOGGING
uint32
dhd_get_rtt_len(void *ndev, dhd_pub_t *dhdp)
{
dhd_info_t *dhd_info;
log_dump_section_hdr_t sec_hdr;
int length = 0;
dhd_dbg_ring_t *ring;
if (ndev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)ndev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return length;
if (logdump_rtt_enable && dhdp->rtt_dbg_ring) {
ring = (dhd_dbg_ring_t *)dhdp->rtt_dbg_ring;
length = ring->ring_size + strlen(RTT_LOG_HDR) + sizeof(sec_hdr);
}
return length;
}
#endif /* EWP_RTT_LOGGING */
#ifdef EWP_DACS
uint32
dhd_get_init_dump_len(void *ndev, dhd_pub_t *dhdp, int section)
{
uint32 length = 0;
log_dump_section_hdr_t sec_hdr;
dhd_info_t *dhd_info;
if (ndev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)ndev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return length;
if (!dhdp->ewp_dacs_fw_enable)
return length;
switch (section) {
case LOG_DUMP_SECTION_EWP_HW_INIT_LOG:
if (dhdp->ewphw_initlog_buf) {
length += dhdp->ewphw_initlog_len;
}
length += (uint32)(strlen(EWP_HW_INIT_LOG_HDR) +
sizeof(sec_hdr));
break;
case LOG_DUMP_SECTION_EWP_HW_MOD_DUMP:
if (dhdp->ewphw_moddump_buf) {
length += dhdp->ewphw_moddump_len;
}
length += (uint32)(strlen(EWP_HW_MOD_DUMP_LOG_HDR) +
sizeof(sec_hdr));
break;
case LOG_DUMP_SECTION_EWP_HW_REG_DUMP:
if (dhdp->ewphw_regdump_buf) {
length += dhdp->ewphw_regdump_len;
}
length += (uint32)(strlen(EWP_HW_REG_DUMP_LOG_HDR) +
sizeof(sec_hdr));
break;
default:
break;
}
return length;
}
#endif /* EWP_DACS */
uint32
dhd_get_wrapper_regdump_len(void *ndev, dhd_pub_t *dhdp)
{
dhd_info_t *dhd_info;
log_dump_section_hdr_t sec_hdr;
int length = 0;
if (ndev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)ndev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return length;
if (dhdp->dbg->wrapper_buf.buf && dhdp->dbg->wrapper_buf.len &&
dhdp->dbg->wrapper_regdump_size) {
length = dhdp->dbg->wrapper_regdump_size + strlen(WRAPPER_REG_DUMP_LOG_HDR)
+ sizeof(sec_hdr);
}
return length;
}
uint32
dhd_get_cookie_log_len(void *ndev, dhd_pub_t *dhdp)
{
int length = 0;
dhd_info_t *dhd_info;
if (ndev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)ndev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return length;
if (dhdp->logdump_cookie && dhd_logdump_cookie_count(dhdp) > 0) {
length = dhd_log_dump_cookie_len(dhdp);
}
return length;
}
#ifdef DHD_DUMP_PCIE_RINGS
uint32
dhd_get_flowring_len(void *ndev, dhd_pub_t *dhdp)
{
uint32 length = 0;
log_dump_section_hdr_t sec_hdr;
dhd_info_t *dhd_info;
uint16 max_tx_flowrings;
if (ndev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)ndev);
dhdp = &dhd_info->pub;
}
if (!dhdp) {
return length;
}
max_tx_flowrings = dhd_get_max_flow_rings(dhdp);
if (!max_tx_flowrings) {
DHD_ERROR(("%s() error: zero max_tx_flowrings\n", __FUNCTION__));
return length;
}
length += (uint32) strlen(RING_DUMP_HDR);
length += (uint32) sizeof(sec_hdr);
length += (uint32) sizeof(max_tx_flowrings);
/* max_item and item_size value which is of 4bytes is dumped at
* start of each ring dump, so adding 4bytes to total length.
*/
length += (uint32) ((D2HRING_TXCMPLT_ITEMSIZE * d2h_max_txcpl)
+ (sizeof(uint16) * 2)
+ (H2DRING_RXPOST_ITEMSIZE * h2d_max_rxpost)
+ (sizeof(uint16) * 2)
+ (D2HRING_RXCMPLT_ITEMSIZE * d2h_max_rxcpl)
+ (sizeof(uint16) * 2)
+ (H2DRING_CTRL_SUB_ITEMSIZE * h2d_max_ctrlpost)
+ (sizeof(uint16) * 2)
+ (D2HRING_CTRL_CMPLT_ITEMSIZE * d2h_max_ctrlcpl)
+ (sizeof(uint16) * 2)
#ifdef EWP_EDL
/* EDL ring doesn't have max_item and item_size */
+ (D2HRING_EDL_HDR_SIZE * D2HRING_EDL_MAX_ITEM));
#else
+ (H2DRING_INFO_BUFPOST_ITEMSIZE * H2DRING_DYNAMIC_INFO_MAX_ITEM)
+ (sizeof(uint16) * 2)
+ (D2HRING_INFO_BUFCMPLT_ITEMSIZE * D2HRING_DYNAMIC_INFO_MAX_ITEM)
+ (sizeof(uint16) * 2));
#endif /* EWP_EDL */
if (dhdp->htput_support) {
/* flowring lengths are different for HTPUT rings, handle accordingly */
length += ((dhd_prot_get_h2d_txpost_size(dhdp) * h2d_htput_max_txpost *
dhdp->htput_total_flowrings) +
(dhd_prot_get_h2d_txpost_size(dhdp) * h2d_max_txpost *
(max_tx_flowrings - dhdp->htput_total_flowrings)));
} else {
length += (dhd_prot_get_h2d_txpost_size(dhdp) * h2d_max_txpost *
max_tx_flowrings);
}
length += max_tx_flowrings * (sizeof(uint16) * 2);
return length;
}
#endif /* DHD_DUMP_PCIE_RINGS */
#ifdef EWP_ECNTRS_LOGGING
uint32
dhd_get_ecntrs_len(void *ndev, dhd_pub_t *dhdp)
{
dhd_info_t *dhd_info;
log_dump_section_hdr_t sec_hdr;
int length = 0;
dhd_dbg_ring_t *ring;
if (ndev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)ndev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return length;
if (logdump_ecntr_enable && dhdp->ecntr_dbg_ring) {
ring = (dhd_dbg_ring_t *)dhdp->ecntr_dbg_ring;
length = ring->ring_size + strlen(ECNTRS_LOG_HDR) + sizeof(sec_hdr);
}
return length;
}
#endif /* EWP_ECNTRS_LOGGING */
int
dhd_get_dld_log_dump(void *dev, dhd_pub_t *dhdp, const void *user_buf,
void *fp, uint32 len, int type, void *pos)
{
int ret = BCME_OK;
struct dhd_log_dump_buf *dld_buf;
log_dump_section_hdr_t sec_hdr;
dhd_info_t *dhd_info;
dld_buf = &g_dld_buf[type];
if (dev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)dev);
dhdp = &dhd_info->pub;
} else if (!dhdp) {
return BCME_ERROR;
}
DHD_PRINT(("%s: ENTER \n", __FUNCTION__));
dhd_init_sec_hdr(&sec_hdr);
/* write the section header first */
ret = dhd_export_debug_data(dld_hdrs[type].hdr_str, fp, user_buf,
strlen(dld_hdrs[type].hdr_str), pos);
if (ret < 0)
goto exit;
len -= (uint32)strlen(dld_hdrs[type].hdr_str);
len -= (uint32)sizeof(sec_hdr);
sec_hdr.type = dld_hdrs[type].sec_type;
sec_hdr.length = len;
ret = dhd_export_debug_data((char *)&sec_hdr, fp, user_buf, sizeof(sec_hdr), pos);
if (ret < 0)
goto exit;
ret = dhd_export_debug_data(dld_buf->buffer, fp, user_buf, len, pos);
if (ret < 0)
goto exit;
exit:
return ret;
}
int
dhd_get_debug_dump_file_name(void *dev, dhd_pub_t *dhdp, char *dump_path, int size)
{
int ret;
int len = 0;
dhd_info_t *dhd_info;
struct rtc_time tm;
if (dev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)dev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return BCME_ERROR;
bzero(dump_path, size);
ret = snprintf(dump_path, size, "%s",
DHD_COMMON_DUMP_PATH DHD_DEBUG_DUMP_TYPE);
len += ret;
/* Keep the same timestamp across different dump logs */
clear_debug_dump_time(dhdp->debug_dump_time_str);
get_debug_dump_time(dhdp->debug_dump_time_str);
sscanf(dhdp->debug_dump_time_str, DHD_LOG_DUMP_TS_FMT_YYMMDDHHMMSS,
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec);
ret = snprintf(dump_path + len, size - len, "_" DHD_LOG_DUMP_TS_FMT_YYMMDDHHMMSS,
tm.tm_year, tm.tm_mon, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);
len += ret;
ret = 0;
switch (dhdp->debug_dump_subcmd) {
case CMD_UNWANTED:
ret = snprintf(dump_path + len, size - len, "%s", DHD_DUMP_SUBSTR_UNWANTED);
break;
case CMD_DISCONNECTED:
ret = snprintf(dump_path + len, size - len, "%s", DHD_DUMP_SUBSTR_DISCONNECTED);
break;
default:
break;
}
len += ret;
return BCME_OK;
}
uint32
dhd_get_dld_len(int log_type)
{
unsigned long wr_size = 0;
unsigned long buf_size = 0;
unsigned long flags = 0;
struct dhd_log_dump_buf *dld_buf;
log_dump_section_hdr_t sec_hdr;
/* calculate the length of the log */
dld_buf = &g_dld_buf[log_type];
buf_size = (unsigned long)dld_buf->max -
(unsigned long)dld_buf->buffer;
if (dld_buf->wraparound) {
wr_size = buf_size;
} else {
/* need to hold the lock before accessing 'present' and 'remain' ptrs */
DHD_LOG_DUMP_BUF_LOCK(&dld_buf->lock, flags);
wr_size = (unsigned long)dld_buf->present -
(unsigned long)dld_buf->front;
DHD_LOG_DUMP_BUF_UNLOCK(&dld_buf->lock, flags);
}
return (wr_size + sizeof(sec_hdr) + strlen(dld_hdrs[log_type].hdr_str));
}
static void
dhd_get_time_str(dhd_pub_t *dhdp, char *time_str, int size)
{
char *ts = NULL;
bzero(time_str, size);
ts = dhd_log_dump_get_timestamp();
snprintf(time_str, size,
"\n\n ========== LOG DUMP TAKEN AT : %s =========\n", ts);
}
int
dhd_print_time_str(const void *user_buf, void *fp, uint32 len, void *pos)
{
char *ts = NULL;
int ret = 0;
char time_str[128];
memset_s(time_str, sizeof(time_str), 0, sizeof(time_str));
ts = dhd_log_dump_get_timestamp();
snprintf(time_str, sizeof(time_str),
"\n\n ========== LOG DUMP TAKEN AT : %s =========\n", ts);
/* write the timestamp hdr to the file first */
ret = dhd_export_debug_data(time_str, fp, user_buf, strlen(time_str), pos);
if (ret < 0) {
DHD_ERROR(("write file error, err = %d\n", ret));
}
return ret;
}
#if defined(DHD_FW_COREDUMP) && defined(DNGL_EVENT_SUPPORT)
int
dhd_print_health_chk_data(void *dev, dhd_pub_t *dhdp, const void *user_buf,
void *fp, uint32 len, void *pos)
{
int ret = BCME_OK;
log_dump_section_hdr_t sec_hdr;
dhd_info_t *dhd_info;
if (dev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)dev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return BCME_ERROR;
dhd_init_sec_hdr(&sec_hdr);
if (dhdp->memdump_type == DUMP_TYPE_DONGLE_HOST_EVENT) {
/* write the section header first */
ret = dhd_export_debug_data(HEALTH_CHK_LOG_HDR, fp, user_buf,
strlen(HEALTH_CHK_LOG_HDR), pos);
if (ret < 0)
goto exit;
len -= (uint32)strlen(HEALTH_CHK_LOG_HDR);
sec_hdr.type = LOG_DUMP_SECTION_HEALTH_CHK;
sec_hdr.length = HEALTH_CHK_BUF_SIZE;
ret = dhd_export_debug_data((char *)&sec_hdr, fp, user_buf, sizeof(sec_hdr), pos);
if (ret < 0)
goto exit;
len -= (uint32)sizeof(sec_hdr);
/* write the log */
ret = dhd_export_debug_data((char *)dhdp->health_chk_event_data, fp,
user_buf, len, pos);
if (ret < 0)
goto exit;
}
exit:
return ret;
}
#endif /* DHD_FW_COREDUMP && DNGL_EVENT_SUPPORT */
#if defined(BCMPCIE)
int
dhd_print_ext_trap_data(void *dev, dhd_pub_t *dhdp, const void *user_buf,
void *fp, uint32 len, void *pos)
{
int ret = BCME_OK;
log_dump_section_hdr_t sec_hdr;
dhd_info_t *dhd_info;
if (dev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)dev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return BCME_ERROR;
dhd_init_sec_hdr(&sec_hdr);
/* append extended trap data to the file in case of traps */
if (dhdp->dongle_trap_occured &&
dhdp->extended_trap_data) {
/* write the section header first */
ret = dhd_export_debug_data(EXT_TRAP_LOG_HDR, fp, user_buf,
strlen(EXT_TRAP_LOG_HDR), pos);
if (ret < 0)
goto exit;
len -= (uint32)strlen(EXT_TRAP_LOG_HDR);
sec_hdr.type = LOG_DUMP_SECTION_EXT_TRAP;
sec_hdr.length = BCMPCIE_EXT_TRAP_DATA_MAXLEN;
ret = dhd_export_debug_data((uint8 *)&sec_hdr, fp, user_buf, sizeof(sec_hdr), pos);
if (ret < 0)
goto exit;
len -= (uint32)sizeof(sec_hdr);
/* write the log */
ret = dhd_export_debug_data((uint8 *)dhdp->extended_trap_data, fp,
user_buf, len, pos);
if (ret < 0)
goto exit;
}
exit:
return ret;
}
#endif /* BCMPCIE */
int
dhd_print_dump_data(void *dev, dhd_pub_t *dhdp, const void *user_buf,
void *fp, uint32 len, void *pos)
{
int ret = BCME_OK;
log_dump_section_hdr_t sec_hdr;
dhd_info_t *dhd_info;
if (dev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)dev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return BCME_ERROR;
dhd_init_sec_hdr(&sec_hdr);
ret = dhd_export_debug_data(DHD_DUMP_LOG_HDR, fp, user_buf, strlen(DHD_DUMP_LOG_HDR), pos);
if (ret < 0)
goto exit;
len -= (uint32)strlen(DHD_DUMP_LOG_HDR);
sec_hdr.type = LOG_DUMP_SECTION_DHD_DUMP;
sec_hdr.length = len;
ret = dhd_export_debug_data((char *)&sec_hdr, fp, user_buf, sizeof(sec_hdr), pos);
if (ret < 0)
goto exit;
len -= (uint32)sizeof(sec_hdr);
if (dhdp->concise_dbg_buf) {
dhd_dump(dhdp, (char *)dhdp->concise_dbg_buf, CONCISE_DUMP_BUFLEN);
ret = dhd_export_debug_data(dhdp->concise_dbg_buf, fp, user_buf, len, pos);
if (ret < 0)
goto exit;
}
exit:
return ret;
}
#ifdef EWP_DACS
int
dhd_print_init_dump_data(void *dev, dhd_pub_t *dhdp, const void *user_buf,
void *fp, uint32 len, void *pos, int section)
{
int ret = BCME_OK;
log_dump_section_hdr_t sec_hdr;
dhd_info_t *dhd_info;
char *sechdr_str = NULL;
uint8 *buf = NULL;
if (dev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)dev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return BCME_ERROR;
if (section == LOG_DUMP_SECTION_EWP_HW_INIT_LOG) {
sechdr_str = EWP_HW_INIT_LOG_HDR;
buf = dhdp->ewphw_initlog_buf;
} else if (section == LOG_DUMP_SECTION_EWP_HW_MOD_DUMP) {
sechdr_str = EWP_HW_MOD_DUMP_LOG_HDR;
buf = dhdp->ewphw_moddump_buf;
} else if (section == LOG_DUMP_SECTION_EWP_HW_REG_DUMP) {
sechdr_str = EWP_HW_REG_DUMP_LOG_HDR;
buf = dhdp->ewphw_regdump_buf;
} else {
return BCME_ERROR;
}
dhd_init_sec_hdr(&sec_hdr);
ret = dhd_export_debug_data(sechdr_str, fp, user_buf,
strlen(sechdr_str), pos);
if (ret < 0)
goto exit;
len -= (uint32)strlen(sechdr_str);
sec_hdr.type = section;
sec_hdr.length = len;
ret = dhd_export_debug_data((char *)&sec_hdr, fp, user_buf, sizeof(sec_hdr), pos);
if (ret < 0)
goto exit;
len -= (uint32)sizeof(sec_hdr);
if (buf) {
ret = dhd_export_debug_data(buf, fp, user_buf, len, pos);
if (ret < 0)
goto exit;
}
exit:
return ret;
}
#endif /* EWP_DACS */
/* Generic function to print a binary buffer to debug_dump */
int
dhd_print_any_buffer_data(void *dev, dhd_pub_t *dhdp, const void *user_buf,
void *fp, uint32 len, void *pos, int section, char *sechdr_str, uint8 *buf)
{
int ret = BCME_OK;
log_dump_section_hdr_t sec_hdr;
dhd_info_t *dhd_info;
if (dev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)dev);
dhdp = &dhd_info->pub;
}
if (!dhdp || !buf || !sechdr_str)
return BCME_ERROR;
dhd_init_sec_hdr(&sec_hdr);
ret = dhd_export_debug_data(sechdr_str, fp, user_buf,
strlen(sechdr_str), pos);
if (ret < 0)
goto exit;
len -= (uint32)strlen(sechdr_str);
sec_hdr.type = section;
sec_hdr.length = len;
ret = dhd_export_debug_data((char *)&sec_hdr, fp, user_buf, sizeof(sec_hdr), pos);
if (ret < 0)
goto exit;
len -= (uint32)sizeof(sec_hdr);
if (buf) {
ret = dhd_export_debug_data(buf, fp, user_buf, len, pos);
if (ret < 0)
goto exit;
}
exit:
return ret;
}
int
dhd_print_cookie_data(void *dev, dhd_pub_t *dhdp, const void *user_buf,
void *fp, uint32 len, void *pos)
{
int ret = BCME_OK;
dhd_info_t *dhd_info;
if (dev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)dev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return BCME_ERROR;
if (dhdp->logdump_cookie && dhd_logdump_cookie_count(dhdp) > 0) {
ret = dhd_log_dump_cookie_to_file(dhdp, fp, user_buf, (unsigned long *)pos);
}
return ret;
}
#ifdef DHD_DUMP_PCIE_RINGS
int
dhd_print_flowring_data(void *dev, dhd_pub_t *dhdp, const void *user_buf,
void *fp, uint32 len, void *pos)
{
log_dump_section_hdr_t sec_hdr;
int ret = BCME_OK;
uint16 max_tx_flowrings;
dhd_info_t *dhd_info;
if (dev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)dev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return BCME_ERROR;
dhd_init_sec_hdr(&sec_hdr);
/* write the section header first */
ret = dhd_export_debug_data(RING_DUMP_HDR, fp, user_buf,
strlen(RING_DUMP_HDR), pos);
if (ret < 0)
goto exit;
len -= strlen(RING_DUMP_HDR);
sec_hdr.type = LOG_DUMP_SECTION_RING;
sec_hdr.length = len - sizeof(sec_hdr);
ret = dhd_export_debug_data((char *)&sec_hdr, fp, user_buf, sizeof(sec_hdr), pos);
if (ret < 0)
goto exit;
max_tx_flowrings = dhd_get_max_flow_rings(dhdp);
if (!max_tx_flowrings) {
DHD_ERROR(("%s() error: zero max_tx_flowrings\n", __FUNCTION__));
goto exit;
}
/* write the number of max_tx_flowrings after section header */
ret = dhd_export_debug_data((char *)&max_tx_flowrings, fp, user_buf,
sizeof(max_tx_flowrings), pos);
if (ret < 0)
goto exit;
/* write the log */
ret = dhd_d2h_h2d_ring_dump(dhdp, fp, user_buf, (unsigned long *)pos, TRUE);
if (ret < 0)
goto exit;
exit:
return ret;
}
#endif /* DHD_DUMP_PCIE_RINGS */
#ifdef EWP_ECNTRS_LOGGING
int
dhd_print_ecntrs_data(void *dev, dhd_pub_t *dhdp, const void *user_buf,
void *fp, uint32 len, void *pos)
{
log_dump_section_hdr_t sec_hdr;
int ret = BCME_OK;
dhd_info_t *dhd_info;
if (dev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)dev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return BCME_ERROR;
dhd_init_sec_hdr(&sec_hdr);
if (logdump_ecntr_enable &&
dhdp->ecntr_dbg_ring) {
sec_hdr.type = LOG_DUMP_SECTION_ECNTRS;
ret = dhd_dump_debug_ring(dhdp, dhdp->ecntr_dbg_ring,
user_buf, &sec_hdr, ECNTRS_LOG_HDR, len, LOG_DUMP_SECTION_ECNTRS);
}
return ret;
}
#endif /* EWP_ECNTRS_LOGGING */
#ifdef EWP_RTT_LOGGING
int
dhd_print_rtt_data(void *dev, dhd_pub_t *dhdp, const void *user_buf,
void *fp, uint32 len, void *pos)
{
log_dump_section_hdr_t sec_hdr;
int ret = BCME_OK;
dhd_info_t *dhd_info;
if (dev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)dev);
dhdp = &dhd_info->pub;
}
if (!dhdp)
return BCME_ERROR;
dhd_init_sec_hdr(&sec_hdr);
if (logdump_rtt_enable && dhdp->rtt_dbg_ring) {
ret = dhd_dump_debug_ring(dhdp, dhdp->rtt_dbg_ring,
user_buf, &sec_hdr, RTT_LOG_HDR, len, LOG_DUMP_SECTION_RTT);
}
return ret;
}
#endif /* EWP_RTT_LOGGING */
#ifdef DHD_STATUS_LOGGING
int
dhd_print_status_log_data(void *dev, dhd_pub_t *dhdp, const void *user_buf,
void *fp, uint32 len, void *pos)
{
dhd_info_t *dhd_info;
if (dev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)dev);
dhdp = &dhd_info->pub;
}
if (!dhdp) {
return BCME_ERROR;
}
return dhd_statlog_write_logdump(dhdp, user_buf, fp, len, pos);
}
uint32
dhd_get_status_log_len(void *ndev, dhd_pub_t *dhdp)
{
dhd_info_t *dhd_info;
uint32 length = 0;
if (ndev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)ndev);
dhdp = &dhd_info->pub;
}
if (dhdp) {
length = dhd_statlog_get_logbuf_len(dhdp);
}
return length;
}
#endif /* DHD_STATUS_LOGGING */
#ifdef DHD_MAP_PKTID_LOGGING
uint32
dhd_get_pktid_map_logging_len(void *ndev, dhd_pub_t *dhdp, bool is_map)
{
dhd_info_t *dhd_info;
log_dump_section_hdr_t sec_hdr;
uint32 length = 0;
if (ndev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)ndev);
dhdp = &dhd_info->pub;
}
if (!dhdp || !dhdp->enable_pktid_log_dump) {
return length;
}
length = dhd_pktid_buf_len(dhdp, is_map) + sizeof(sec_hdr);
return length;
}
int
dhd_print_pktid_map_log_data(void *dev, dhd_pub_t *dhdp, const void *user_buf,
void *fp, uint32 len, void *pos, bool is_map)
{
dhd_info_t *dhd_info;
if (dev) {
dhd_info = *(dhd_info_t **)netdev_priv((struct net_device *)dev);
dhdp = &dhd_info->pub;
}
if (!dhdp) {
return BCME_ERROR;
}
return dhd_write_pktid_log_dump(dhdp, user_buf, fp, len, pos, is_map);
}
#endif /* DHD_MAP_PKTID_LOGGING */
void
dhd_init_sec_hdr(log_dump_section_hdr_t *sec_hdr)
{
/* prep the section header */
bzero(sec_hdr, sizeof(*sec_hdr));
sec_hdr->magic = LOG_DUMP_MAGIC;
sec_hdr->timestamp = local_clock();
}
/* Must hold 'dhd_os_logdump_lock' before calling this function ! */
int
do_dhd_log_dump(dhd_pub_t *dhdp, log_dump_type_t *type)
{
int ret = 0, i = 0;
struct file *fp = NULL;
mm_segment_t fs;
loff_t pos = 0;
char dump_path[128];
uint32 file_mode;
unsigned long flags = 0;
struct kstat stat;
char time_str[128];
unsigned int len = 0;
log_dump_section_hdr_t sec_hdr;
DHD_PRINT(("%s: ENTER \n", __FUNCTION__));
DHD_GENERAL_LOCK(dhdp, flags);
if (DHD_BUS_CHECK_DOWN_OR_DOWN_IN_PROGRESS(dhdp)) {
DHD_GENERAL_UNLOCK(dhdp, flags);
DHD_ERROR(("%s: bus is down! can't collect log dump. \n", __FUNCTION__));
goto exit1;
}
DHD_BUS_BUSY_SET_IN_LOGDUMP(dhdp);
DHD_GENERAL_UNLOCK(dhdp, flags);
if ((ret = dhd_log_flush(dhdp, type)) < 0) {
goto exit1;
}
GETFS_AND_SETFS_TO_KERNEL_DS(fs);
dhd_get_debug_dump_file_name(NULL, dhdp, dump_path, sizeof(dump_path));
DHD_PRINT(("debug_dump_path = %s\n", dump_path));
DHD_PRINT(("DHD version: %s\n", dhd_version));
DHD_PRINT(("F/W version: %s\n", fw_version));
dhd_log_dump_buf_addr(dhdp, type);
dhd_get_time_str(dhdp, time_str, 128);
file_mode = O_CREAT | O_WRONLY | O_SYNC | O_TRUNC;
fp = dhd_filp_open(dump_path, file_mode, 0664);
if (IS_ERR(fp) || (fp == NULL)) {
/* If android installed image, try '/data' directory */
#if defined(CONFIG_X86) && defined(OEM_ANDROID)
DHD_ERROR(("%s: File open error on Installed android image, trying /data...\n",
__FUNCTION__));
snprintf(dump_path, sizeof(dump_path), "/data/" DHD_DEBUG_DUMP_TYPE);
snprintf(dump_path + strlen(dump_path),
sizeof(dump_path) - strlen(dump_path),
"_%s", dhdp->debug_dump_time_str);
fp = dhd_filp_open(dump_path, file_mode, 0664);
if (IS_ERR(fp) || (fp == NULL)) {
ret = PTR_ERR(fp);
DHD_ERROR(("open file error, err = %d\n", ret));
goto exit2;
}
DHD_PRINT(("debug_dump_path = %s\n", dump_path));
#endif /* defined(CONFIG_X86) && defined(OEM_ANDROID) */
#if !(defined(CONFIG_X86) && defined(OEM_ANDROID))
ret = PTR_ERR(fp);
DHD_ERROR(("open file error, err = %d\n", ret));
goto exit2;
#endif /* CONFIG_X86 && OEM_ANDROID */
}
ret = dhd_vfs_stat(dump_path, &stat);
if (ret < 0) {
DHD_ERROR(("file stat error, err = %d\n", ret));
goto exit2;
}
dhd_print_time_str(0, fp, len, &pos);
for (i = 0; i < DLD_BUFFER_NUM; ++i) {
if (*type != DLD_BUF_TYPE_ALL && i != *type)
continue;
len = dhd_get_dld_len(i);
dhd_get_dld_log_dump(NULL, dhdp, 0, fp, len, i, &pos);
if (*type != DLD_BUF_TYPE_ALL)
break;
}
#ifdef EWP_ECNTRS_LOGGING
if (*type == DLD_BUF_TYPE_ALL &&
logdump_ecntr_enable &&
dhdp->ecntr_dbg_ring) {
dhd_log_dump_ring_to_file(dhdp, dhdp->ecntr_dbg_ring,
fp, (unsigned long *)&pos,
&sec_hdr, ECNTRS_LOG_HDR, LOG_DUMP_SECTION_ECNTRS);
}
#endif /* EWP_ECNTRS_LOGGING */
#ifdef EWP_DACS
for (i = LOG_DUMP_SECTION_EWP_HW_INIT_LOG; i <= LOG_DUMP_SECTION_EWP_HW_REG_DUMP; ++i) {
len = dhd_get_init_dump_len(NULL, dhdp, i);
if (len) {
if (dhd_print_init_dump_data(NULL, dhdp, 0, fp,
len, &pos, i) < 0)
goto exit2;
}
}
#endif /* EWP_DACS */
#ifdef DHD_STATUS_LOGGING
if (dhdp->statlog) {
/* write the statlog */
len = dhd_get_status_log_len(NULL, dhdp);
if (len) {
if (dhd_print_status_log_data(NULL, dhdp, 0, fp,
len, &pos) < 0) {
goto exit2;
}
}
}
#endif /* DHD_STATUS_LOGGING */
#ifdef DHD_STATUS_LOGGING
if (dhdp->statlog) {
dhd_print_buf_addr(dhdp, "statlog_logbuf", dhd_statlog_get_logbuf(dhdp),
dhd_statlog_get_logbuf_len(dhdp));
}
#endif /* DHD_STATUS_LOGGING */
#ifdef EWP_RTT_LOGGING
if (*type == DLD_BUF_TYPE_ALL &&
logdump_rtt_enable &&
dhdp->rtt_dbg_ring) {
dhd_log_dump_ring_to_file(dhdp, dhdp->rtt_dbg_ring,
fp, (unsigned long *)&pos,
&sec_hdr, RTT_LOG_HDR, LOG_DUMP_SECTION_RTT);
}
#endif /* EWP_RTT_LOGGING */
#ifdef EWP_BCM_TRACE
if (*type == DLD_BUF_TYPE_ALL &&
dhdp->bcm_trace_dbg_ring) {
dhd_log_dump_ring_to_file(dhdp, dhdp->bcm_trace_dbg_ring,
fp, (unsigned long *)&pos,
&sec_hdr, BCM_TRACE_LOG_HDR, LOG_DUMP_SECTION_BCM_TRACE);
}
#endif /* EWP_BCM_TRACE */
#ifdef EWP_CX_TIMELINE
if (*type == DLD_BUF_TYPE_ALL && dhdp->cx_timeline_dbg_ring) {
dhd_log_dump_ring_to_file(dhdp, dhdp->cx_timeline_dbg_ring,
fp, (unsigned long *)&pos,
&sec_hdr, CX_TIMELINE_LOG_HDR, LOG_DUMP_SECTION_COEX_TIMELINE);
}
#endif /* EWP_CX_TIMELINE */
#ifdef BCMPCIE
len = dhd_get_ext_trap_len(NULL, dhdp);
if (len) {
if (dhd_print_ext_trap_data(NULL, dhdp, 0, fp, len, &pos) < 0)
goto exit2;
}
#endif /* BCMPCIE */
#if defined(DHD_FW_COREDUMP) && defined (DNGL_EVENT_SUPPORT)
len = dhd_get_health_chk_len(NULL, dhdp);
if (len) {
if (dhd_print_health_chk_data(NULL, dhdp, 0, fp, len, &pos) < 0)
goto exit2;
}
#endif /* DHD_FW_COREDUMP && DNGL_EVENT_SUPPORT */
len = dhd_get_dhd_dump_len(NULL, dhdp);
if (len) {
if (dhd_print_dump_data(NULL, dhdp, 0, fp, len, &pos) < 0)
goto exit2;
}
len = dhd_get_cookie_log_len(NULL, dhdp);
if (len) {
if (dhd_print_cookie_data(NULL, dhdp, 0, fp, len, &pos) < 0)
goto exit2;
}
len = dhd_get_wrapper_regdump_len(NULL, dhdp);
if (len) {
if (dhd_print_any_buffer_data(NULL, dhdp, NULL, fp, len, &pos,
LOG_DUMP_SECTION_WRAPPER_REG_DUMP, WRAPPER_REG_DUMP_LOG_HDR,
dhdp->dbg->wrapper_buf.buf) < 0) {
goto exit2;
}
}
#ifdef DHD_DUMP_PCIE_RINGS
len = dhd_get_flowring_len(NULL, dhdp);
if (len) {
if (dhd_print_flowring_data(NULL, dhdp, 0, fp, len, &pos) < 0)
goto exit2;
}
#endif /* DHD_DUMP_PCIE_RINGS */
#ifdef DHD_MAP_PKTID_LOGGING
/* dump pktid data for dma map */
len = dhd_get_pktid_map_logging_len(NULL, dhdp, TRUE);
if (len) {
if (dhd_print_pktid_map_log_data(NULL, dhdp, NULL,
fp, len, &pos, TRUE) < 0) {
goto exit2;
}
}
/* dump pktid data for dma unmap */
len = dhd_get_pktid_map_logging_len(NULL, dhdp, FALSE);
if (len) {
if (dhd_print_pktid_map_log_data(NULL, dhdp, NULL,
fp, len, &pos, FALSE) < 0) {
goto exit2;
}
}
#endif /* DHD_MAP_PKTID_LOGGING */
exit2:
if (!IS_ERR(fp) && fp != NULL) {
dhd_filp_close(fp, NULL);
DHD_ERROR(("%s: Finished writing log dump to file - '%s' \n",
__FUNCTION__, dump_path));
}
SETFS(fs);
exit1:
if (type) {
MFREE(dhdp->osh, type, sizeof(*type));
}
DHD_GENERAL_LOCK(dhdp, flags);
DHD_BUS_BUSY_CLEAR_IN_LOGDUMP(dhdp);
dhd_os_busbusy_wake(dhdp);
DHD_GENERAL_UNLOCK(dhdp, flags);
#ifdef DHD_DUMP_MNGR
if (ret >= 0) {
dhd_dump_file_manage_enqueue(dhdp, dump_path, DHD_DEBUG_DUMP_TYPE);
}
#endif /* DHD_DUMP_MNGR */
return (ret < 0) ? BCME_ERROR : BCME_OK;
}
bool
dhd_log_dump_ecntr_enabled(void)
{
return (bool)logdump_ecntr_enable;
}
bool
dhd_log_dump_rtt_enabled(void)
{
return (bool)logdump_rtt_enable;
}
void
dhd_log_dump_init(dhd_pub_t *dhd)
{
struct dhd_log_dump_buf *dld_buf, *dld_buf_special;
int i = 0;
uint8 *prealloc_buf = NULL, *bufptr = NULL;
#if defined(CONFIG_DHD_USE_STATIC_BUF) && defined(DHD_USE_STATIC_MEMDUMP)
int prealloc_idx = DHD_PREALLOC_DHD_LOG_DUMP_BUF;
#endif /* CONFIG_DHD_USE_STATIC_BUF && DHD_USE_STATIC_MEMDUMP */
int ret;
dhd_dbg_ring_t *ring = NULL;
unsigned long flags = 0;
dhd_info_t *dhd_info = dhd->info;
#if defined(EWP_ECNTRS_LOGGING)
void *cookie_buf = NULL;
#endif
BCM_REFERENCE(ret);
BCM_REFERENCE(ring);
BCM_REFERENCE(flags);
/* sanity check */
if (logdump_prsrv_tailsize <= 0 ||
logdump_prsrv_tailsize > DHD_LOG_DUMP_MAX_TAIL_FLUSH_SIZE) {
logdump_prsrv_tailsize = DHD_LOG_DUMP_MAX_TAIL_FLUSH_SIZE;
}
/* now adjust the preserve log flush size based on the
* kernel printk log buffer size
*/
#ifdef CONFIG_LOG_BUF_SHIFT
DHD_PRINT(("%s: kernel log buf size = %uKB; logdump_prsrv_tailsize = %uKB;"
" limit prsrv tail size to = %uKB\n",
__FUNCTION__, (1 << CONFIG_LOG_BUF_SHIFT)/1024,
logdump_prsrv_tailsize/1024, LOG_DUMP_KERNEL_TAIL_FLUSH_SIZE/1024));
if (logdump_prsrv_tailsize > LOG_DUMP_KERNEL_TAIL_FLUSH_SIZE) {
logdump_prsrv_tailsize = LOG_DUMP_KERNEL_TAIL_FLUSH_SIZE;
}
#else
DHD_PRINT(("%s: logdump_prsrv_tailsize = %uKB \n",
__FUNCTION__, logdump_prsrv_tailsize/1024));
#endif /* CONFIG_LOG_BUF_SHIFT */
mutex_init(&dhd_info->logdump_lock);
/* initialize log dump buf structures */
bzero(g_dld_buf, sizeof(struct dhd_log_dump_buf) * DLD_BUFFER_NUM);
/* set the log dump buffer size based on the module_param */
if (logdump_max_bufsize > LOG_DUMP_GENERAL_MAX_BUFSIZE ||
logdump_max_bufsize <= 0)
dld_buf_size[DLD_BUF_TYPE_GENERAL] = LOG_DUMP_GENERAL_MAX_BUFSIZE;
else
dld_buf_size[DLD_BUF_TYPE_GENERAL] = logdump_max_bufsize;
/* pre-alloc the memory for the log buffers & 'special' buffer */
dld_buf_special = &g_dld_buf[DLD_BUF_TYPE_SPECIAL];
#if defined(CONFIG_DHD_USE_STATIC_BUF) && defined(DHD_USE_STATIC_MEMDUMP)
prealloc_buf = DHD_OS_PREALLOC(dhd, prealloc_idx++, LOG_DUMP_TOTAL_BUFSIZE);
dld_buf_special->buffer = DHD_OS_PREALLOC(dhd, prealloc_idx++,
dld_buf_size[DLD_BUF_TYPE_SPECIAL]);
#else
prealloc_buf = MALLOCZ(dhd->osh, LOG_DUMP_TOTAL_BUFSIZE);
dld_buf_special->buffer = MALLOCZ(dhd->osh, dld_buf_size[DLD_BUF_TYPE_SPECIAL]);
#endif /* CONFIG_DHD_USE_STATIC_BUF && DHD_USE_STATIC_MEMDUMP */
if (!prealloc_buf) {
DHD_ERROR(("Failed to allocate memory for log buffers\n"));
goto fail;
}
if (!dld_buf_special->buffer) {
DHD_ERROR(("Failed to allocate memory for special buffer\n"));
goto fail;
}
bufptr = prealloc_buf;
for (i = 0; i < DLD_BUFFER_NUM; i++) {
dld_buf = &g_dld_buf[i];
dld_buf->dhd_pub = dhd;
spin_lock_init(&dld_buf->lock);
dld_buf->wraparound = 0;
if (i != DLD_BUF_TYPE_SPECIAL) {
dld_buf->buffer = bufptr;
dld_buf->max = (unsigned long)dld_buf->buffer + dld_buf_size[i];
bufptr = (uint8 *)dld_buf->max;
} else {
dld_buf->max = (unsigned long)dld_buf->buffer + dld_buf_size[i];
}
dld_buf->present = dld_buf->front = dld_buf->buffer;
dld_buf->remain = dld_buf_size[i];
dld_buf->enable = 1;
}
/* now use the rest of the pre-alloc'd memory for other rings */
#ifdef EWP_ECNTRS_LOGGING
dhd->ecntr_dbg_ring = dhd_dbg_ring_alloc_init(dhd,
ECNTR_RING_ID, ECNTR_RING_NAME,
LOG_DUMP_ECNTRS_MAX_BUFSIZE,
bufptr, TRUE);
if (!dhd->ecntr_dbg_ring) {
DHD_ERROR(("%s: unable to init ecounters dbg ring !\n",
__FUNCTION__));
goto fail;
}
bufptr += LOG_DUMP_ECNTRS_MAX_BUFSIZE;
#endif /* EWP_ECNTRS_LOGGING */
#ifdef EWP_RTT_LOGGING
dhd->rtt_dbg_ring = dhd_dbg_ring_alloc_init(dhd,
RTT_RING_ID, RTT_RING_NAME,
LOG_DUMP_RTT_MAX_BUFSIZE,
bufptr, TRUE);
if (!dhd->rtt_dbg_ring) {
DHD_ERROR(("%s: unable to init rtt dbg ring !\n",
__FUNCTION__));
goto fail;
}
bufptr += LOG_DUMP_RTT_MAX_BUFSIZE;
#endif /* EWP_RTT_LOGGING */
#ifdef EWP_BCM_TRACE
dhd->bcm_trace_dbg_ring = dhd_dbg_ring_alloc_init(dhd,
BCM_TRACE_RING_ID, BCM_TRACE_RING_NAME,
LOG_DUMP_BCM_TRACE_MAX_BUFSIZE,
bufptr, TRUE);
if (!dhd->bcm_trace_dbg_ring) {
DHD_ERROR(("%s: unable to init bcm trace dbg ring !\n",
__FUNCTION__));
goto fail;
}
bufptr += LOG_DUMP_BCM_TRACE_MAX_BUFSIZE;
#endif /* EWP_BCM_TRACE */
#ifdef EWP_EVENTTS_LOG
dhd->eventts_buf = VMALLOCZ(dhd->osh, LOG_DUMP_EVENTTS_BUFSIZE);
if (!dhd->eventts_buf) {
DHD_ERROR(("%s: unable to alloc mem for eventts_dbg_ring !\n",
__FUNCTION__));
goto fail;
} else {
dhd->eventts_dbg_ring = dhd_dbg_ring_alloc_init(dhd,
EVENTTS_RING_ID, EVENTTS_RING_NAME,
LOG_DUMP_EVENTTS_BUFSIZE,
dhd->eventts_buf, TRUE);
if (!dhd->eventts_dbg_ring) {
DHD_ERROR(("%s: unable to init eventts_dbg_ring !\n",
__FUNCTION__));
goto fail;
}
}
#endif /* EWP_EVENTTS_LOG */
#ifdef EWP_CX_TIMELINE
dhd->cx_timeline_dbg_ring_buf = VMALLOCZ(dhd->osh, LOG_DUMP_CX_TIMELINE_BUFSIZE);
if (!dhd->cx_timeline_dbg_ring_buf) {
DHD_ERROR(("%s: unable to alloc mem for cx_timeline_dbg_ring!\n",
__FUNCTION__));
goto fail;
}
dhd->cx_timeline_dbg_ring = dhd_dbg_ring_alloc_init(dhd,
CX_TIMELINE_RING_ID, CX_TIMELINE_RING_NAME,
LOG_DUMP_CX_TIMELINE_BUFSIZE,
dhd->cx_timeline_dbg_ring_buf, TRUE);
if (!dhd->cx_timeline_dbg_ring) {
DHD_ERROR(("%s: unable to init cx_timeline_dbg_ring !\n",
__FUNCTION__));
goto fail;
}
#endif /* EWP_CX_TIMELINE */
/* Concise buffer is used as intermediate buffer for following purposes
* a) pull ecounters records temporarily before
* writing it to file
* b) to store dhd dump data before putting it to file
* It should have a size equal to
* MAX(largest possible ecntr record, 'dhd dump' data size)
*/
dhd->concise_dbg_buf = MALLOC(dhd->osh, CONCISE_DUMP_BUFLEN);
if (!dhd->concise_dbg_buf) {
DHD_ERROR(("%s: unable to alloc mem for concise debug info !\n",
__FUNCTION__));
goto fail;
}
#ifdef EWP_DACS
/* initialize to default lengths */
dhd->ewphw_initlog_len = EWP_HW_INIT_LOG_LEN;
dhd->ewphw_regdump_len = EWP_HW_REG_DUMP_LEN;
dhd->ewphw_moddump_len = EWP_HW_MOD_DUMP_LEN;
dhd->ewphw_buf_totlen = dhd->ewphw_initlog_len + dhd->ewphw_regdump_len +
dhd->ewphw_moddump_len;
/* init dump buffer will hold fw init logs - part of DACS */
dhd->ewphw_initlog_buf = VMALLOCZ(dhd->osh, dhd->ewphw_buf_totlen);
if (!dhd->ewphw_initlog_buf) {
DHD_ERROR(("%s: unable to alloc mem for init dump buf !\n",
__FUNCTION__));
goto fail;
}
DHD_PRINT(("%s: ewphw - default initlog_len=%u; regdump_len=%u; moddump_len=%u\n",
__FUNCTION__, dhd->ewphw_initlog_len, dhd->ewphw_regdump_len,
dhd->ewphw_moddump_len));
dhd->ewphw_regdump_buf = dhd->ewphw_initlog_buf +
(unsigned long)dhd->ewphw_initlog_len;
dhd->ewphw_moddump_buf = dhd->ewphw_regdump_buf +
(unsigned long)dhd->ewphw_regdump_len;
#endif /* EWP_DACS */
#if defined(DHD_EVENT_LOG_FILTER)
/* init filter last, because filter use buffer which alloced by log dump */
ret = dhd_event_log_filter_init(dhd,
bufptr,
LOG_DUMP_FILTER_MAX_BUFSIZE);
if (ret != BCME_OK) {
goto fail;
}
#endif /* DHD_EVENT_LOG_FILTER */
#if defined(EWP_ECNTRS_LOGGING)
cookie_buf = MALLOC(dhd->osh, LOG_DUMP_COOKIE_BUFSIZE);
if (!cookie_buf) {
DHD_ERROR(("%s: unable to alloc mem for logdump cookie buffer\n",
__FUNCTION__));
goto fail;
}
ret = dhd_logdump_cookie_init(dhd, cookie_buf, LOG_DUMP_COOKIE_BUFSIZE);
if (ret != BCME_OK) {
MFREE(dhd->osh, cookie_buf, LOG_DUMP_COOKIE_BUFSIZE);
goto fail;
}
#endif /* EWP_ECNTRS_LOGGING */
return;
fail:
#if defined(DHD_EVENT_LOG_FILTER)
/* deinit filter first, because filter use buffer which alloced by log dump */
if (dhd->event_log_filter) {
dhd_event_log_filter_deinit(dhd);
}
#endif /* DHD_EVENT_LOG_FILTER */
if (dhd->concise_dbg_buf) {
MFREE(dhd->osh, dhd->concise_dbg_buf, CONCISE_DUMP_BUFLEN);
}
#ifdef EWP_DACS
if (dhd->ewphw_initlog_buf) {
VMFREE(dhd->osh, dhd->ewphw_initlog_buf, dhd->ewphw_buf_totlen);
}
#endif /* EWP_DACS */
#ifdef EWP_ECNTRS_LOGGING
if (dhd->logdump_cookie) {
dhd_logdump_cookie_deinit(dhd);
MFREE(dhd->osh, dhd->logdump_cookie, LOG_DUMP_COOKIE_BUFSIZE);
dhd->logdump_cookie = NULL;
}
#endif /* EWP_ECNTRS_LOGGING */
#if defined(CONFIG_DHD_USE_STATIC_BUF) && defined(DHD_USE_STATIC_MEMDUMP)
if (prealloc_buf) {
DHD_OS_PREFREE(dhd, prealloc_buf, LOG_DUMP_TOTAL_BUFSIZE);
}
if (dld_buf_special->buffer) {
DHD_OS_PREFREE(dhd, dld_buf_special->buffer,
dld_buf_size[DLD_BUF_TYPE_SPECIAL]);
}
#else
if (prealloc_buf) {
MFREE(dhd->osh, prealloc_buf, LOG_DUMP_TOTAL_BUFSIZE);
}
if (dld_buf_special->buffer) {
MFREE(dhd->osh, dld_buf_special->buffer,
dld_buf_size[DLD_BUF_TYPE_SPECIAL]);
}
#endif /* CONFIG_DHD_USE_STATIC_BUF */
for (i = 0; i < DLD_BUFFER_NUM; i++) {
dld_buf = &g_dld_buf[i];
dld_buf->enable = 0;
dld_buf->buffer = NULL;
}
mutex_destroy(&dhd_info->logdump_lock);
#ifdef EWP_EVENTTS_LOG
if (dhd->eventts_buf)
VMFREE(dhd->osh, dhd->eventts_buf, LOG_DUMP_EVENTTS_BUFSIZE);
#endif /* EWP_EVENTTS_LOG */
#ifdef EWP_CX_TIMELINE
if (dhd->cx_timeline_dbg_ring_buf) {
VMFREE(dhd->osh, dhd->cx_timeline_dbg_ring_buf, LOG_DUMP_CX_TIMELINE_BUFSIZE);
}
#endif /* EWP_CX_TIMELINE */
}
void
dhd_log_dump_deinit(dhd_pub_t *dhd)
{
struct dhd_log_dump_buf *dld_buf = NULL, *dld_buf_special = NULL;
int i = 0;
dhd_info_t *dhd_info = dhd->info;
dhd_dbg_ring_t *ring = NULL;
BCM_REFERENCE(ring);
if (dhd->concise_dbg_buf) {
MFREE(dhd->osh, dhd->concise_dbg_buf, CONCISE_DUMP_BUFLEN);
dhd->concise_dbg_buf = NULL;
}
#ifdef EWP_DACS
if (dhd->ewphw_initlog_buf) {
VMFREE(dhd->osh, dhd->ewphw_initlog_buf, dhd->ewphw_buf_totlen);
dhd->ewphw_initlog_buf = NULL;
}
dhd->ewp_dacs_fw_enable = FALSE;
#endif /* EWP_DACS */
#ifdef EWP_ECNTRS_LOGGING
if (dhd->logdump_cookie) {
dhd_logdump_cookie_deinit(dhd);
MFREE(dhd->osh, dhd->logdump_cookie, LOG_DUMP_COOKIE_BUFSIZE);
dhd->logdump_cookie = NULL;
}
if (dhd->ecntr_dbg_ring) {
dhd_dbg_ring_dealloc_deinit(&dhd->ecntr_dbg_ring, dhd);
}
#endif /* EWP_ECNTRS_LOGGING */
#ifdef EWP_RTT_LOGGING
if (dhd->rtt_dbg_ring) {
dhd_dbg_ring_dealloc_deinit(&dhd->rtt_dbg_ring, dhd);
}
#endif /* EWP_RTT_LOGGING */
#ifdef EWP_BCM_TRACE
if (dhd->bcm_trace_dbg_ring) {
dhd_dbg_ring_dealloc_deinit(&dhd->bcm_trace_dbg_ring, dhd);
}
#endif /* EWP_BCM_TRACE */
#ifdef EWP_EVENTTS_LOG
if (dhd->eventts_dbg_ring)
dhd_dbg_ring_dealloc_deinit(&dhd->eventts_dbg_ring, dhd);
if (dhd->eventts_buf)
VMFREE(dhd->osh, dhd->eventts_buf, LOG_DUMP_EVENTTS_BUFSIZE);
#endif /* EWP_EVENTTS_LOG */
#ifdef EWP_CX_TIMELINE
if (dhd->cx_timeline_dbg_ring) {
dhd_dbg_ring_dealloc_deinit(&dhd->cx_timeline_dbg_ring, dhd);
}
if (dhd->cx_timeline_dbg_ring_buf) {
VMFREE(dhd->osh, dhd->cx_timeline_dbg_ring_buf, LOG_DUMP_CX_TIMELINE_BUFSIZE);
}
#endif /* EWP_CX_TIMELINE */
/* 'general' buffer points to start of the pre-alloc'd memory */
dld_buf = &g_dld_buf[DLD_BUF_TYPE_GENERAL];
dld_buf_special = &g_dld_buf[DLD_BUF_TYPE_SPECIAL];
#if defined(CONFIG_DHD_USE_STATIC_BUF) && defined(DHD_USE_STATIC_MEMDUMP)
if (dld_buf->buffer) {
DHD_OS_PREFREE(dhd, dld_buf->buffer, LOG_DUMP_TOTAL_BUFSIZE);
}
if (dld_buf_special->buffer) {
DHD_OS_PREFREE(dhd, dld_buf_special->buffer,
dld_buf_size[DLD_BUF_TYPE_SPECIAL]);
}
#else
if (dld_buf->buffer) {
MFREE(dhd->osh, dld_buf->buffer, LOG_DUMP_TOTAL_BUFSIZE);
}
if (dld_buf_special->buffer) {
MFREE(dhd->osh, dld_buf_special->buffer,
dld_buf_size[DLD_BUF_TYPE_SPECIAL]);
}
#endif /* CONFIG_DHD_USE_STATIC_BUF */
for (i = 0; i < DLD_BUFFER_NUM; i++) {
dld_buf = &g_dld_buf[i];
dld_buf->enable = 0;
dld_buf->buffer = NULL;
}
mutex_destroy(&dhd_info->logdump_lock);
}
void
dhd_log_dump_write(int type, char *binary_data,
int binary_len, const char *fmt, ...)
{
int len = 0;
char tmp_buf[DHD_LOG_DUMP_MAX_TEMP_BUFFER_SIZE] = {0, };
va_list args;
unsigned long flags = 0;
struct dhd_log_dump_buf *dld_buf = NULL;
if (type < 0 || type >= DLD_BUFFER_NUM) {
DHD_INFO(("%s: Unsupported DHD_LOG_DUMP_BUF_TYPE(%d).\n",
__FUNCTION__, type));
return;
}
dld_buf = &g_dld_buf[type];
if (dld_buf->enable != 1) {
return;
}
va_start(args, fmt);
len = vsnprintf(tmp_buf, DHD_LOG_DUMP_MAX_TEMP_BUFFER_SIZE, fmt, args);
/* Non ANSI C99 compliant returns -1,
* ANSI compliant return len >= DHD_LOG_DUMP_MAX_TEMP_BUFFER_SIZE
*/
va_end(args);
if (len < 0) {
return;
}
if (len >= DHD_LOG_DUMP_MAX_TEMP_BUFFER_SIZE) {
len = DHD_LOG_DUMP_MAX_TEMP_BUFFER_SIZE - 1;
tmp_buf[len] = '\0';
}
/* make a critical section to eliminate race conditions */
DHD_LOG_DUMP_BUF_LOCK(&dld_buf->lock, flags);
if (dld_buf->remain < len) {
dld_buf->wraparound = 1;
dld_buf->present = dld_buf->front;
dld_buf->remain = dld_buf_size[type];
}
memcpy(dld_buf->present, tmp_buf, len);
dld_buf->remain -= len;
dld_buf->present += len;
DHD_LOG_DUMP_BUF_UNLOCK(&dld_buf->lock, flags);
/* double check invalid memory operation */
ASSERT((unsigned long)dld_buf->present <= dld_buf->max);
}
char*
dhd_log_dump_get_timestamp(void)
{
static char buf[32];
u64 ts_nsec;
unsigned long rem_nsec;
ts_nsec = local_clock();
rem_nsec = DIV_AND_MOD_U64_BY_U32(ts_nsec, NSEC_PER_SEC);
snprintf(buf, sizeof(buf), "%5lu.%06lu",
(unsigned long)ts_nsec, rem_nsec / NSEC_PER_USEC);
return buf;
}
void
dhd_log_dump_vendor_trigger(dhd_pub_t *dhd_pub)
{
unsigned long flags = 0;
DHD_GENERAL_LOCK(dhd_pub, flags);
DHD_BUS_BUSY_SET_IN_DUMP_DONGLE_MEM(dhd_pub);
DHD_GENERAL_UNLOCK(dhd_pub, flags);
dhd_log_dump_trigger(dhd_pub, CMD_DEFAULT);
DHD_GENERAL_LOCK(dhd_pub, flags);
DHD_BUS_BUSY_CLEAR_IN_DUMP_DONGLE_MEM(dhd_pub);
dhd_os_busbusy_wake(dhd_pub);
DHD_GENERAL_UNLOCK(dhd_pub, flags);
return;
}
#ifdef DEBUGABILITY
/* coredump triggered by host/user */
void
dhd_coredump_trigger(dhd_pub_t *dhdp)
{
if (!dhdp) {
DHD_ERROR(("dhdp is NULL !\n"));
return;
}
#if (defined(BCMPCIE) || defined(BCMSDIO)) && defined(DHD_FW_COREDUMP)
dhdp->memdump_type = DUMP_TYPE_COREDUMP_BY_USER;
dhd_bus_mem_dump(dhdp);
#endif /* BCMPCIE && DHD_FW_COREDUMP */
}
#endif /* DEBUGABILITY */
void
dhd_log_dump_trigger(dhd_pub_t *dhdp, int subcmd)
{
#if defined(DHD_DUMP_FILE_WRITE_FROM_KERNEL)
log_dump_type_t *flush_type;
#endif /* DHD_DUMP_FILE_WRITE_FROM_KERNEL */
uint64 current_time_sec;
if (!dhdp) {
DHD_ERROR(("dhdp is NULL !\n"));
goto exit;
}
if (subcmd >= CMD_MAX || subcmd < CMD_DEFAULT) {
DHD_ERROR(("%s : Invalid subcmd \n", __FUNCTION__));
goto exit;
}
current_time_sec = DIV_U64_BY_U32(OSL_LOCALTIME_NS(), NSEC_PER_SEC);
DHD_PRINT(("%s: current_time_sec=%lld debug_dump_time_sec=%lld interval=%d\n",
__FUNCTION__, current_time_sec, dhdp->debug_dump_time_sec,
DEBUG_DUMP_TRIGGER_INTERVAL_SEC));
if ((current_time_sec - dhdp->debug_dump_time_sec) < DEBUG_DUMP_TRIGGER_INTERVAL_SEC) {
DHD_ERROR(("%s : Last debug dump triggered(%lld) within %d seconds, so SKIP\n",
__FUNCTION__, dhdp->debug_dump_time_sec, DEBUG_DUMP_TRIGGER_INTERVAL_SEC));
goto exit;
}
clear_debug_dump_time(dhdp->debug_dump_time_str);
#ifdef DHD_PCIE_RUNTIMEPM
/* wake up RPM if SYSDUMP is triggered */
dhdpcie_runtime_bus_wake(dhdp, TRUE, __builtin_return_address(0));
#endif /* DHD_PCIE_RUNTIMEPM */
/* */
dhdp->debug_dump_subcmd = subcmd;
dhdp->debug_dump_time_sec = DIV_U64_BY_U32(OSL_LOCALTIME_NS(), NSEC_PER_SEC);
#if defined(DHD_DUMP_FILE_WRITE_FROM_KERNEL)
/* flush_type is freed at do_dhd_log_dump function */
flush_type = MALLOCZ(dhdp->osh, sizeof(log_dump_type_t));
if (flush_type) {
*flush_type = DLD_BUF_TYPE_ALL;
dhd_schedule_log_dump(dhdp, flush_type);
} else {
DHD_ERROR(("%s Fail to malloc flush_type\n", __FUNCTION__));
goto exit;
}
#endif /* DHD_DUMP_FILE_WRITE_FROM_KERNEL */
#if defined(DHD_SDTC_ETB_DUMP) && defined(BCMQT_HW)
/* For QT/zebu memdump collection is disabled,
* but set collect_sdtc true here, so that
* developers can issue 'log_dump' dhd iovar
* to get etb/sdtc dumps
*/
DHD_ERROR(("%s : QT/FPGA set collect_sdtc as TRUE\n", __FUNCTION__));
dhdp->collect_sdtc = TRUE;
#endif /* DHD_SDTC_ETB_DUMP && BCMQT_HW */
/* Inside dhd_mem_dump, event notification will be sent to HAL and
* from other context DHD pushes memdump, debug_dump and pktlog dump
* to HAL and HAL will write into file
*/
#if (defined(BCMPCIE) || defined(BCMSDIO)) && defined(DHD_FW_COREDUMP)
if (dhdp->memdump_enabled) {
dhdp->memdump_type = DUMP_TYPE_BY_SYSDUMP;
dhd_bus_mem_dump(dhdp);
}
#endif /* BCMPCIE && DHD_FW_COREDUMP */
exit:
dhdp->skip_memdump_map_read = FALSE;
return;
}
#ifdef DHD_DEBUGABILITY_DEBUG_DUMP
int dhd_debug_dump_get_ring_num(int sec_type)
{
int idx = 0;
for (idx = 0; idx < ARRAYSIZE(dhd_debug_dump_ring_map); idx++) {
if (dhd_debug_dump_ring_map[idx].type == sec_type) {
idx = dhd_debug_dump_ring_map[idx].debug_dump_ring;
return idx;
}
}
return idx;
}
#endif /* DHD_DEBUGABILITY_DEBUG_DUMP */
int
dhd_dump_debug_ring(dhd_pub_t *dhdp, void *ring_ptr, const void *user_buf,
log_dump_section_hdr_t *sec_hdr,
char *text_hdr, int buflen, uint32 sec_type)
{
uint32 rlen = 0;
uint32 data_len = 0;
void *data = NULL;
unsigned long flags = 0;
int ret = 0;
dhd_dbg_ring_t *ring = (dhd_dbg_ring_t *)ring_ptr;
#ifndef DHD_DEBUGABILITY_DEBUG_DUMP
int pos = 0;
#endif /* !DHD_DEBUGABILITY_DEBUG_DUMP */
int fpos_sechdr = 0;
int tot_len = 0;
char *tmp_buf = NULL;
int idx;
int ring_num = 0;
BCM_REFERENCE(idx);
BCM_REFERENCE(tot_len);
BCM_REFERENCE(fpos_sechdr);
BCM_REFERENCE(data_len);
BCM_REFERENCE(tmp_buf);
BCM_REFERENCE(ring_num);
#ifdef DHD_DEBUGABILITY_DEBUG_DUMP
if (!dhdp || !ring || !sec_hdr || !text_hdr) {
return BCME_BADARG;
}
tmp_buf = (char *)VMALLOCZ(dhdp->osh, ring->ring_size);
if (!tmp_buf) {
DHD_ERROR(("%s: VMALLOC Fail id:%d size:%u\n",
__func__, ring->id, ring->ring_size));
return BCME_NOMEM;
}
#else
if (!dhdp || !ring || !user_buf || !sec_hdr || !text_hdr) {
return BCME_BADARG;
}
#endif /* DHD_DEBUGABILITY_DEBUG_DUMP */
/* do not allow further writes to the ring
* till we flush it
*/
DHD_DBG_RING_LOCK(ring->lock, flags);
ring->state = RING_SUSPEND;
DHD_DBG_RING_UNLOCK(ring->lock, flags);
#ifdef DHD_DEBUGABILITY_DEBUG_DUMP
ring_num = dhd_debug_dump_get_ring_num(sec_type);
dhd_export_debug_data(text_hdr, NULL, NULL, strlen(text_hdr), &ring_num);
data = tmp_buf;
do {
rlen = dhd_dbg_ring_pull_single(ring, data, ring->ring_size, TRUE);
if (rlen > 0) {
tot_len += rlen;
data += rlen;
}
} while ((rlen > 0));
sec_hdr->type = sec_type;
sec_hdr->length = tot_len;
DHD_PRINT(("%s: DUMP id:%d type:%u tot_len:%d\n", __func__, ring->id, sec_type, tot_len));
dhd_export_debug_data((char *)sec_hdr, NULL, NULL, sizeof(*sec_hdr), &ring_num);
dhd_export_debug_data(tmp_buf, NULL, NULL, tot_len, &ring_num);
VMFREE(dhdp->osh, tmp_buf, ring->ring_size);
#else
if (dhdp->concise_dbg_buf) {
/* re-use concise debug buffer temporarily
* to pull ring data, to write
* record by record to file
*/
data_len = CONCISE_DUMP_BUFLEN;
data = dhdp->concise_dbg_buf;
ret = dhd_export_debug_data(text_hdr, NULL, user_buf, strlen(text_hdr), &pos);
/* write the section header now with zero length,
* once the correct length is found out, update
* it later
*/
fpos_sechdr = pos;
sec_hdr->type = sec_type;
sec_hdr->length = 0;
ret = dhd_export_debug_data((char *)sec_hdr, NULL, user_buf,
sizeof(*sec_hdr), &pos);
do {
rlen = dhd_dbg_ring_pull_single(ring, data, data_len, TRUE);
if (rlen > 0) {
/* write the log */
ret = dhd_export_debug_data(data, NULL, user_buf, rlen, &pos);
}
DHD_DBGIF(("%s: rlen : %d\n", __FUNCTION__, rlen));
} while ((rlen > 0));
/* now update the section header length in the file */
/* Complete ring size is dumped by HAL, hence updating length to ring size */
sec_hdr->length = ring->ring_size;
ret = dhd_export_debug_data((char *)sec_hdr, NULL, user_buf,
sizeof(*sec_hdr), &fpos_sechdr);
} else {
DHD_ERROR(("%s: No concise buffer available !\n", __FUNCTION__));
}
#endif /* DHD_DEBUGABILITY_DEBUG_DUMP */
DHD_DBG_RING_LOCK(ring->lock, flags);
ring->state = RING_ACTIVE;
/* Resetting both read and write pointer,
* since all items are read.
*/
ring->rp = ring->wp = 0;
DHD_DBG_RING_UNLOCK(ring->lock, flags);
return ret;
}
int
dhd_log_dump_ring_to_file(dhd_pub_t *dhdp, void *ring_ptr, void *file,
unsigned long *file_posn, log_dump_section_hdr_t *sec_hdr,
char *text_hdr, uint32 sec_type)
{
uint32 rlen = 0;
uint32 data_len = 0, total_len = 0;
void *data = NULL;
unsigned long fpos_sechdr = 0;
unsigned long flags = 0;
int ret = 0;
dhd_dbg_ring_t *ring = (dhd_dbg_ring_t *)ring_ptr;
if (!dhdp || !ring || !file || !sec_hdr ||
!file_posn || !text_hdr)
return BCME_BADARG;
/* do not allow further writes to the ring
* till we flush it
*/
DHD_DBG_RING_LOCK(ring->lock, flags);
ring->state = RING_SUSPEND;
DHD_DBG_RING_UNLOCK(ring->lock, flags);
if (dhdp->concise_dbg_buf) {
/* re-use concise debug buffer temporarily
* to pull ring data, to write
* record by record to file
*/
data_len = CONCISE_DUMP_BUFLEN;
data = dhdp->concise_dbg_buf;
dhd_os_write_file_posn(file, file_posn, text_hdr,
strlen(text_hdr));
/* write the section header now with zero length,
* once the correct length is found out, update
* it later
*/
dhd_init_sec_hdr(sec_hdr);
fpos_sechdr = *file_posn;
sec_hdr->type = sec_type;
sec_hdr->length = 0;
dhd_os_write_file_posn(file, file_posn, (char *)sec_hdr,
sizeof(*sec_hdr));
do {
rlen = dhd_dbg_ring_pull_single(ring, data, data_len, TRUE);
if (rlen > 0) {
/* write the log */
ret = dhd_os_write_file_posn(file, file_posn, data, rlen);
if (ret < 0) {
DHD_ERROR(("%s: write file error !\n", __FUNCTION__));
DHD_DBG_RING_LOCK(ring->lock, flags);
ring->state = RING_ACTIVE;
DHD_DBG_RING_UNLOCK(ring->lock, flags);
return BCME_ERROR;
}
}
total_len += rlen;
} while (rlen > 0);
/* now update the section header length in the file */
sec_hdr->length = total_len;
dhd_os_write_file_posn(file, &fpos_sechdr, (char *)sec_hdr, sizeof(*sec_hdr));
} else {
DHD_ERROR(("%s: No concise buffer available !\n", __FUNCTION__));
}
DHD_DBG_RING_LOCK(ring->lock, flags);
ring->state = RING_ACTIVE;
/* Resetting both read and write pointer,
* since all items are read.
*/
ring->rp = ring->wp = 0;
DHD_DBG_RING_UNLOCK(ring->lock, flags);
return BCME_OK;
}
/* logdump cookie */
#define MAX_LOGUDMP_COOKIE_CNT 10u
#define LOGDUMP_COOKIE_STR_LEN 50u
int
dhd_logdump_cookie_init(dhd_pub_t *dhdp, uint8 *buf, uint32 buf_size)
{
uint32 ring_size;
if (!dhdp || !buf) {
DHD_ERROR(("INVALID PTR: dhdp:%p buf:%p\n", dhdp, buf));
return BCME_ERROR;
}
ring_size = dhd_ring_get_hdr_size() + LOGDUMP_COOKIE_STR_LEN * MAX_LOGUDMP_COOKIE_CNT;
if (buf_size < ring_size) {
DHD_ERROR(("BUF SIZE IS TO SHORT: req:%d buf_size:%d\n",
ring_size, buf_size));
return BCME_ERROR;
}
dhdp->logdump_cookie = dhd_ring_init(dhdp, buf, buf_size,
LOGDUMP_COOKIE_STR_LEN, MAX_LOGUDMP_COOKIE_CNT,
DHD_RING_TYPE_FIXED);
if (!dhdp->logdump_cookie) {
DHD_ERROR(("FAIL TO INIT COOKIE RING\n"));
return BCME_ERROR;
}
return BCME_OK;
}
void
dhd_logdump_cookie_deinit(dhd_pub_t *dhdp)
{
if (!dhdp) {
return;
}
if (dhdp->logdump_cookie) {
dhd_ring_deinit(dhdp, dhdp->logdump_cookie);
}
return;
}
void
dhd_logdump_cookie_save(dhd_pub_t *dhdp, char *cookie, char *type)
{
char *ptr;
if (!dhdp || !cookie || !type || !dhdp->logdump_cookie) {
DHD_ERROR(("%s: At least one buffer ptr is NULL dhdp=%p cookie=%p"
" type = %p, cookie_cfg:%p\n", __FUNCTION__,
dhdp, cookie, type, dhdp?dhdp->logdump_cookie: NULL));
return;
}
ptr = (char *)dhd_ring_get_empty(dhdp->logdump_cookie);
if (ptr == NULL) {
DHD_ERROR(("%s : Skip to save due to locking\n", __FUNCTION__));
return;
}
scnprintf(ptr, LOGDUMP_COOKIE_STR_LEN, "%s: %s\n", type, cookie);
return;
}
int
dhd_logdump_cookie_get(dhd_pub_t *dhdp, char *ret_cookie, uint32 buf_size)
{
char *ptr;
if (!dhdp || !ret_cookie || !dhdp->logdump_cookie) {
DHD_ERROR(("%s: At least one buffer ptr is NULL dhdp=%p"
"cookie=%p cookie_cfg:%p\n", __FUNCTION__,
dhdp, ret_cookie, dhdp?dhdp->logdump_cookie: NULL));
return BCME_ERROR;
}
ptr = (char *)dhd_ring_get_first(dhdp->logdump_cookie);
if (ptr == NULL) {
DHD_ERROR(("%s : Skip to save due to locking\n", __FUNCTION__));
return BCME_ERROR;
}
memcpy(ret_cookie, ptr, MIN(buf_size, strlen(ptr)));
dhd_ring_free_first(dhdp->logdump_cookie);
return BCME_OK;
}
int
dhd_logdump_cookie_count(dhd_pub_t *dhdp)
{
if (!dhdp || !dhdp->logdump_cookie) {
DHD_ERROR(("%s: At least one buffer ptr is NULL dhdp=%p cookie=%p\n",
__FUNCTION__, dhdp, dhdp?dhdp->logdump_cookie: NULL));
return 0;
}
return dhd_ring_get_cur_size(dhdp->logdump_cookie);
}
static inline int
__dhd_log_dump_cookie_to_file(
dhd_pub_t *dhdp, void *fp, const void *user_buf, unsigned long *f_pos,
char *buf, uint32 buf_size)
{
uint32 remain = buf_size;
int ret = BCME_ERROR;
char tmp_buf[LOGDUMP_COOKIE_STR_LEN];
log_dump_section_hdr_t sec_hdr;
uint32 read_idx;
uint32 write_idx;
read_idx = dhd_ring_get_read_idx(dhdp->logdump_cookie);
write_idx = dhd_ring_get_write_idx(dhdp->logdump_cookie);
while (dhd_logdump_cookie_count(dhdp) > 0) {
bzero(tmp_buf, sizeof(tmp_buf));
ret = dhd_logdump_cookie_get(dhdp, tmp_buf, LOGDUMP_COOKIE_STR_LEN);
if (ret != BCME_OK) {
return ret;
}
remain -= scnprintf(&buf[buf_size - remain], remain, "%s", tmp_buf);
}
dhd_ring_set_read_idx(dhdp->logdump_cookie, read_idx);
dhd_ring_set_write_idx(dhdp->logdump_cookie, write_idx);
ret = dhd_export_debug_data(COOKIE_LOG_HDR, fp, user_buf, strlen(COOKIE_LOG_HDR), f_pos);
if (ret < 0) {
DHD_ERROR(("%s : Write file Error for cookie hdr\n", __FUNCTION__));
return ret;
}
sec_hdr.magic = LOG_DUMP_MAGIC;
sec_hdr.timestamp = local_clock();
sec_hdr.type = LOG_DUMP_SECTION_COOKIE;
sec_hdr.length = buf_size - remain;
ret = dhd_export_debug_data((char *)&sec_hdr, fp, user_buf, sizeof(sec_hdr), f_pos);
if (ret < 0) {
DHD_ERROR(("%s : Write file Error for section hdr\n", __FUNCTION__));
return ret;
}
ret = dhd_export_debug_data(buf, fp, user_buf, sec_hdr.length, f_pos);
if (ret < 0) {
DHD_ERROR(("%s : Write file Error for cookie data\n", __FUNCTION__));
}
return ret;
}
uint32
dhd_log_dump_cookie_len(dhd_pub_t *dhdp)
{
int len = 0;
char tmp_buf[LOGDUMP_COOKIE_STR_LEN];
log_dump_section_hdr_t sec_hdr;
char *buf = NULL;
int ret = BCME_ERROR;
uint32 buf_size = MAX_LOGUDMP_COOKIE_CNT * LOGDUMP_COOKIE_STR_LEN;
uint32 read_idx;
uint32 write_idx;
uint32 remain;
remain = buf_size;
if (!dhdp || !dhdp->logdump_cookie) {
DHD_ERROR(("%s At least one ptr is NULL "
"dhdp = %p cookie %p\n",
__FUNCTION__, dhdp, dhdp?dhdp->logdump_cookie:NULL));
goto exit;
}
buf = (char *)MALLOCZ(dhdp->osh, buf_size);
if (!buf) {
DHD_ERROR(("%s Fail to malloc buffer\n", __FUNCTION__));
goto exit;
}
read_idx = dhd_ring_get_read_idx(dhdp->logdump_cookie);
write_idx = dhd_ring_get_write_idx(dhdp->logdump_cookie);
while (dhd_logdump_cookie_count(dhdp) > 0) {
bzero(tmp_buf, sizeof(tmp_buf));
ret = dhd_logdump_cookie_get(dhdp, tmp_buf, LOGDUMP_COOKIE_STR_LEN);
if (ret != BCME_OK) {
goto exit;
}
remain -= (uint32)strlen(tmp_buf);
}
dhd_ring_set_read_idx(dhdp->logdump_cookie, read_idx);
dhd_ring_set_write_idx(dhdp->logdump_cookie, write_idx);
len += strlen(COOKIE_LOG_HDR);
len += sizeof(sec_hdr);
len += (buf_size - remain);
exit:
if (buf)
MFREE(dhdp->osh, buf, buf_size);
return len;
}
int
dhd_log_dump_cookie(dhd_pub_t *dhdp, const void *user_buf)
{
int ret = BCME_ERROR;
char tmp_buf[LOGDUMP_COOKIE_STR_LEN];
log_dump_section_hdr_t sec_hdr;
char *buf = NULL;
uint32 buf_size = MAX_LOGUDMP_COOKIE_CNT * LOGDUMP_COOKIE_STR_LEN;
int pos = 0;
uint32 read_idx;
uint32 write_idx;
uint32 remain;
remain = buf_size;
if (!dhdp || !dhdp->logdump_cookie) {
DHD_ERROR(("%s At least one ptr is NULL "
"dhdp = %p cookie %p\n",
__FUNCTION__, dhdp, dhdp?dhdp->logdump_cookie:NULL));
goto exit;
}
buf = (char *)MALLOCZ(dhdp->osh, buf_size);
if (!buf) {
DHD_ERROR(("%s Fail to malloc buffer\n", __FUNCTION__));
goto exit;
}
read_idx = dhd_ring_get_read_idx(dhdp->logdump_cookie);
write_idx = dhd_ring_get_write_idx(dhdp->logdump_cookie);
while (dhd_logdump_cookie_count(dhdp) > 0) {
bzero(tmp_buf, sizeof(tmp_buf));
ret = dhd_logdump_cookie_get(dhdp, tmp_buf, LOGDUMP_COOKIE_STR_LEN);
if (ret != BCME_OK) {
goto exit;
}
remain -= scnprintf(&buf[buf_size - remain], remain, "%s", tmp_buf);
}
dhd_ring_set_read_idx(dhdp->logdump_cookie, read_idx);
dhd_ring_set_write_idx(dhdp->logdump_cookie, write_idx);
ret = dhd_export_debug_data(COOKIE_LOG_HDR, NULL, user_buf, strlen(COOKIE_LOG_HDR), &pos);
sec_hdr.magic = LOG_DUMP_MAGIC;
sec_hdr.timestamp = local_clock();
sec_hdr.type = LOG_DUMP_SECTION_COOKIE;
sec_hdr.length = buf_size - remain;
ret = dhd_export_debug_data((char *)&sec_hdr, NULL, user_buf, sizeof(sec_hdr), &pos);
ret = dhd_export_debug_data(buf, NULL, user_buf, sec_hdr.length, &pos);
exit:
if (buf)
MFREE(dhdp->osh, buf, buf_size);
return ret;
}
int
dhd_log_dump_cookie_to_file(dhd_pub_t *dhdp, void *fp, const void *user_buf, unsigned long *f_pos)
{
char *buf;
int ret = BCME_ERROR;
uint32 buf_size = MAX_LOGUDMP_COOKIE_CNT * LOGDUMP_COOKIE_STR_LEN;
#ifdef DHD_DEBUGABILITY_DEBUG_DUMP
if (!dhdp || !dhdp->logdump_cookie) {
#else
if (!dhdp || !dhdp->logdump_cookie || (!fp && !user_buf) || !f_pos) {
#endif /* DHD_DEBUGABILITY_DEBUG_DUMP */
DHD_ERROR(("%s At least one ptr is NULL "
"dhdp = %p cookie %p fp = %p f_pos = %p\n",
__FUNCTION__, dhdp, dhdp?dhdp->logdump_cookie:NULL, fp, f_pos));
return ret;
}
buf = (char *)MALLOCZ(dhdp->osh, buf_size);
if (!buf) {
DHD_ERROR(("%s Fail to malloc buffer\n", __FUNCTION__));
return ret;
}
ret = __dhd_log_dump_cookie_to_file(dhdp, fp, user_buf, f_pos, buf, buf_size);
MFREE(dhdp->osh, buf, buf_size);
return ret;
}
void
get_debug_dump_time(char *str)
{
struct timespec64 curtime;
unsigned long long local_time;
struct rtc_time tm;
if (!strlen(str)) {
ktime_get_real_ts64(&curtime);
local_time = (u64)(curtime.tv_sec -
(sys_tz.tz_minuteswest * DHD_LOG_DUMP_TS_MULTIPLIER_VALUE));
#if (LINUX_VERSION_CODE >= KERNEL_VERSION (3, 19, 0))
rtc_time64_to_tm(local_time, &tm);
#else
rtc_time_to_tm(local_time, &tm);
#endif /* LINUX_VER >= 3.19.0 */
snprintf(str, DEBUG_DUMP_TIME_BUF_LEN, DHD_LOG_DUMP_TS_FMT_YYMMDDHHMMSSMSMS,
tm.tm_year - 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,
tm.tm_sec, curtime.tv_nsec/NSEC_PER_MSEC);
}
}
void
clear_debug_dump_time(char *str)
{
bzero(str, DEBUG_DUMP_TIME_BUF_LEN);
}
#if defined(WL_CFGVENDOR_SEND_HANG_EVENT) || defined(DHD_PKT_LOGGING)
void
copy_debug_dump_time(char *dest, char *src)
{
memcpy(dest, src, DEBUG_DUMP_TIME_BUF_LEN);
}
#endif /* WL_CFGVENDOR_SEND_HANG_EVENT || DHD_PKT_LOGGING */
#ifdef DHD_IOVAR_LOG_FILTER_DUMP
typedef struct iovar_log_filter_table {
char command[64];
int enable;
} iovar_log_filter_table_t;
/* WLC_GET_VAR is not being logged by default. Add for logging in debug_dump. */
static const iovar_log_filter_table_t iovar_get_filter_params[] = {
{"cur_etheraddr", TRUE},
{"\0", TRUE}
};
/* WLC_SET_VAR is being logged by default. Add for not logging in debug_dump. */
static const iovar_log_filter_table_t iovar_set_filter_params[] = {
{"pkt_filter_enable", FALSE},
{"pkt_filter_mode", FALSE},
{"\0", FALSE}
};
bool
dhd_iovar_log_dump_check(dhd_pub_t *dhd_pub, uint32 cmd, char *msg)
{
int cnt = 0;
const iovar_log_filter_table_t *table;
bool ret_val = TRUE;
/* Logging all IOVARs with DHD_IOVAR_MEM() in debug_dump file
* during during Wifi ON.
*/
if (dhd_pub->up == FALSE) {
return TRUE;
}
if (cmd == WLC_GET_VAR) {
ret_val = FALSE;
table = iovar_get_filter_params;
} else if (cmd == WLC_SET_VAR) {
ret_val = TRUE;
table = iovar_set_filter_params;
} else {
return TRUE;
}
while (strlen(table[cnt].command) > 0) {
if (!strncmp(msg, table[cnt].command,
strlen(table[cnt].command))) {
return table[cnt].enable;
}
cnt++;
}
return ret_val;
}
#endif /* DHD_IOVAR_LOG_FILTER_DUMP */
#endif /* DHD_LOG_DUMP */