| /* |
| * DHD debugability: Status Information Logging support |
| * |
| * Copyright (C) 2020, 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/Open:>> |
| * |
| * $Id$ |
| */ |
| |
| #include <typedefs.h> |
| #include <osl.h> |
| #include <bcmutils.h> |
| #include <ethernet.h> |
| #include <bcmevent.h> |
| #include <dngl_stats.h> |
| #include <dhd.h> |
| #include <dhd_dbg.h> |
| #include <linux/rtc.h> |
| |
| #ifdef DHD_STATUS_LOGGING |
| |
| #define DHD_STATLOG_ERR_INTERNAL(fmt, ...) DHD_ERROR(("STATLOG-" fmt, ##__VA_ARGS__)) |
| #define DHD_STATLOG_INFO_INTERNAL(fmt, ...) DHD_INFO(("STATLOG-" fmt, ##__VA_ARGS__)) |
| |
| #define DHD_STATLOG_PRINT(x) DHD_ERROR(x) |
| #define DHD_STATLOG_ERR(x) DHD_STATLOG_ERR_INTERNAL x |
| #define DHD_STATLOG_INFO(x) DHD_STATLOG_INFO_INTERNAL x |
| #define DHD_STATLOG_VALID(stat) (((stat) > (ST(INVALID))) && ((stat) < (ST(MAX)))) |
| |
| dhd_statlog_handle_t * |
| dhd_attach_statlog(dhd_pub_t *dhdp, uint32 num_items, uint32 bdlog_num_items, uint32 logbuf_len) |
| { |
| dhd_statlog_t *statlog = NULL; |
| void *buf = NULL; |
| |
| if (!dhdp) { |
| DHD_STATLOG_ERR(("%s: dhdp is NULL\n", __FUNCTION__)); |
| return NULL; |
| } |
| |
| statlog = (dhd_statlog_t *)VMALLOCZ(dhdp->osh, sizeof(dhd_statlog_t)); |
| if (!statlog) { |
| DHD_STATLOG_ERR(("%s: failed to allocate memory for dhd_statlog_t\n", |
| __FUNCTION__)); |
| return NULL; |
| } |
| |
| /* allocate log buffer */ |
| statlog->logbuf = (uint8 *)VMALLOCZ(dhdp->osh, logbuf_len); |
| if (!statlog->logbuf) { |
| DHD_STATLOG_ERR(("%s: failed to alloc log buffer\n", __FUNCTION__)); |
| goto error; |
| } |
| statlog->logbuf_len = logbuf_len; |
| |
| /* alloc ring buffer */ |
| statlog->bufsize = (uint32)(dhd_ring_get_hdr_size() + |
| DHD_STATLOG_RING_SIZE(num_items)); |
| buf = VMALLOCZ(dhdp->osh, statlog->bufsize); |
| if (!buf) { |
| DHD_STATLOG_ERR(("%s: failed to allocate memory for ring buffer\n", |
| __FUNCTION__)); |
| goto error; |
| } |
| |
| statlog->ringbuf = dhd_ring_init(dhdp, buf, statlog->bufsize, |
| DHD_STATLOG_ITEM_SIZE, num_items, DHD_RING_TYPE_SINGLE_IDX); |
| if (!statlog->ringbuf) { |
| DHD_STATLOG_ERR(("%s: failed to init ring buffer\n", __FUNCTION__)); |
| VMFREE(dhdp->osh, buf, statlog->bufsize); |
| goto error; |
| } |
| |
| /* alloc ring buffer for bigdata logging */ |
| statlog->bdlog_bufsize = (uint32)(dhd_ring_get_hdr_size() + |
| DHD_STATLOG_RING_SIZE(bdlog_num_items)); |
| buf = VMALLOCZ(dhdp->osh, statlog->bdlog_bufsize); |
| if (!buf) { |
| DHD_STATLOG_ERR(("%s: failed to allocate memory for bigdata logging buffer\n", |
| __FUNCTION__)); |
| goto error; |
| } |
| |
| statlog->bdlog_ringbuf = dhd_ring_init(dhdp, buf, statlog->bdlog_bufsize, |
| DHD_STATLOG_ITEM_SIZE, bdlog_num_items, DHD_RING_TYPE_SINGLE_IDX); |
| if (!statlog->bdlog_ringbuf) { |
| DHD_STATLOG_ERR(("%s: failed to init ring buffer for bigdata logging\n", |
| __FUNCTION__)); |
| VMFREE(dhdp->osh, buf, statlog->bdlog_bufsize); |
| goto error; |
| } |
| |
| return (dhd_statlog_handle_t *)statlog; |
| |
| error: |
| if (statlog->logbuf) { |
| VMFREE(dhdp->osh, statlog->logbuf, logbuf_len); |
| } |
| |
| if (statlog->ringbuf) { |
| dhd_ring_deinit(dhdp, statlog->ringbuf); |
| VMFREE(dhdp->osh, statlog->ringbuf, statlog->bufsize); |
| } |
| |
| if (statlog->bdlog_ringbuf) { |
| dhd_ring_deinit(dhdp, statlog->bdlog_ringbuf); |
| VMFREE(dhdp->osh, statlog->bdlog_ringbuf, statlog->bdlog_bufsize); |
| } |
| |
| if (statlog) { |
| VMFREE(dhdp->osh, statlog, sizeof(dhd_statlog_t)); |
| } |
| |
| return NULL; |
| } |
| |
| void |
| dhd_detach_statlog(dhd_pub_t *dhdp) |
| { |
| dhd_statlog_t *statlog; |
| |
| if (!dhdp) { |
| DHD_STATLOG_ERR(("%s: dhdp is NULL\n", __FUNCTION__)); |
| return; |
| } |
| |
| if (!dhdp->statlog) { |
| DHD_STATLOG_ERR(("%s: statlog is NULL\n", __FUNCTION__)); |
| return; |
| } |
| |
| statlog = (dhd_statlog_t *)(dhdp->statlog); |
| |
| if (statlog->bdlog_ringbuf) { |
| dhd_ring_deinit(dhdp, statlog->bdlog_ringbuf); |
| VMFREE(dhdp->osh, statlog->bdlog_ringbuf, statlog->bdlog_bufsize); |
| } |
| |
| if (statlog->ringbuf) { |
| dhd_ring_deinit(dhdp, statlog->ringbuf); |
| VMFREE(dhdp->osh, statlog->ringbuf, statlog->bufsize); |
| } |
| |
| if (statlog->logbuf) { |
| VMFREE(dhdp->osh, statlog->logbuf, statlog->logbuf_len); |
| } |
| |
| VMFREE(dhdp->osh, statlog, sizeof(dhd_statlog_t)); |
| dhdp->statlog = NULL; |
| } |
| |
| static int |
| dhd_statlog_ring_log(dhd_pub_t *dhdp, uint16 stat, uint8 ifidx, uint8 dir, |
| uint16 status, uint16 reason) |
| { |
| dhd_statlog_t *statlog; |
| stat_elem_t *elem; |
| |
| if (!dhdp || !dhdp->statlog) { |
| DHD_STATLOG_ERR(("%s: dhdp or dhdp->statlog is NULL\n", |
| __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| if (ifidx >= DHD_MAX_IFS) { |
| DHD_STATLOG_ERR(("%s: invalid ifidx %d\n", __FUNCTION__, ifidx)); |
| return BCME_ERROR; |
| } |
| |
| if (!DHD_STATLOG_VALID(stat)) { |
| DHD_STATLOG_ERR(("%s: invalid stat %d\n", __FUNCTION__, stat)); |
| return BCME_ERROR; |
| } |
| |
| statlog = (dhd_statlog_t *)(dhdp->statlog); |
| elem = (stat_elem_t *)dhd_ring_get_empty(statlog->ringbuf); |
| if (!elem) { |
| /* no available slot */ |
| DHD_STATLOG_ERR(("%s: cannot allocate a new element\n", |
| __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| elem->ts_tz = OSL_SYSTZTIME_US(); |
| elem->ts = OSL_LOCALTIME_NS(); |
| elem->stat = stat; |
| elem->ifidx = ifidx; |
| elem->dir = dir; |
| elem->reason = reason; |
| elem->status = status; |
| |
| /* Logging for the bigdata */ |
| if (isset(statlog->bdmask, stat)) { |
| stat_elem_t *elem_bd; |
| elem_bd = (stat_elem_t *)dhd_ring_get_empty(statlog->bdlog_ringbuf); |
| if (!elem_bd) { |
| /* no available slot */ |
| DHD_STATLOG_ERR(("%s: cannot allocate a new element for bigdata\n", |
| __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| bcopy(elem, elem_bd, sizeof(stat_elem_t)); |
| } |
| |
| return BCME_OK; |
| } |
| |
| int |
| dhd_statlog_ring_log_data(dhd_pub_t *dhdp, uint16 stat, uint8 ifidx, |
| uint8 dir, bool cond) |
| { |
| return cond ? dhd_statlog_ring_log(dhdp, stat, ifidx, |
| dir ? STDIR(TX) : STDIR(RX), 0, 0) : BCME_OK; |
| } |
| |
| int |
| dhd_statlog_ring_log_data_reason(dhd_pub_t *dhdp, uint16 stat, |
| uint8 ifidx, uint8 dir, uint16 reason) |
| { |
| return dhd_statlog_ring_log(dhdp, stat, ifidx, |
| dir ? STDIR(TX) : STDIR(RX), 0, reason); |
| } |
| |
| int |
| dhd_statlog_ring_log_ctrl(dhd_pub_t *dhdp, uint16 stat, uint8 ifidx, uint16 reason) |
| { |
| return dhd_statlog_ring_log(dhdp, stat, ifidx, ST(DIR_TX), 0, reason); |
| } |
| |
| int |
| dhd_statlog_process_event(dhd_pub_t *dhdp, int type, uint8 ifidx, |
| uint16 status, uint16 reason, uint16 flags) |
| { |
| int stat = ST(INVALID); |
| uint8 dir = STDIR(RX); |
| |
| if (!dhdp || !dhdp->statlog) { |
| DHD_STATLOG_ERR(("%s: dhdp or dhdp->statlog is NULL\n", |
| __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| switch (type) { |
| case WLC_E_SET_SSID: |
| if (status == WLC_E_STATUS_SUCCESS) { |
| stat = ST(ASSOC_DONE); |
| } else if (status == WLC_E_STATUS_TIMEOUT) { |
| stat = ST(ASSOC_TIMEOUT); |
| } else if (status == WLC_E_STATUS_FAIL) { |
| stat = ST(ASSOC_FAIL); |
| } else if (status == WLC_E_STATUS_NO_ACK) { |
| stat = ST(ASSOC_NO_ACK); |
| } else if (status == WLC_E_STATUS_ABORT) { |
| stat = ST(ASSOC_ABORT); |
| } else if (status == WLC_E_STATUS_UNSOLICITED) { |
| stat = ST(ASSOC_UNSOLICITED); |
| } else if (status == WLC_E_STATUS_NO_NETWORKS) { |
| stat = ST(ASSOC_NO_NETWORKS); |
| } else { |
| stat = ST(ASSOC_OTHERS); |
| } |
| break; |
| case WLC_E_AUTH: |
| if (status == WLC_E_STATUS_SUCCESS) { |
| stat = ST(AUTH_DONE); |
| } else if (status == WLC_E_STATUS_TIMEOUT) { |
| stat = ST(AUTH_TIMEOUT); |
| } else if (status == WLC_E_STATUS_FAIL) { |
| stat = ST(AUTH_FAIL); |
| } else if (status == WLC_E_STATUS_NO_ACK) { |
| stat = ST(AUTH_NO_ACK); |
| } else { |
| stat = ST(AUTH_OTHERS); |
| } |
| dir = STDIR(TX); |
| break; |
| case WLC_E_AUTH_IND: |
| stat = ST(AUTH_DONE); |
| break; |
| case WLC_E_DEAUTH: |
| stat = ST(DEAUTH); |
| dir = STDIR(TX); |
| break; |
| case WLC_E_DEAUTH_IND: |
| stat = ST(DEAUTH); |
| break; |
| case WLC_E_DISASSOC: |
| stat = ST(DISASSOC); |
| dir = STDIR(TX); |
| break; |
| case WLC_E_LINK: |
| if (!(flags & WLC_EVENT_MSG_LINK)) { |
| stat = ST(LINKDOWN); |
| } |
| break; |
| case WLC_E_ROAM_PREP: |
| stat = ST(REASSOC_START); |
| break; |
| case WLC_E_ASSOC_REQ_IE: |
| stat = ST(ASSOC_REQ); |
| dir = STDIR(TX); |
| break; |
| case WLC_E_ASSOC_RESP_IE: |
| stat = ST(ASSOC_RESP); |
| break; |
| case WLC_E_BSSID: |
| if (status == WLC_E_STATUS_SUCCESS) { |
| stat = ST(REASSOC_DONE); |
| } else { |
| stat = ST(REASSOC_DONE_OTHERS); |
| } |
| break; |
| case WLC_E_REASSOC: |
| if (status == WLC_E_STATUS_SUCCESS) { |
| stat = ST(REASSOC_SUCCESS); |
| } else { |
| stat = ST(REASSOC_FAILURE); |
| } |
| dir = STDIR(TX); |
| break; |
| case WLC_E_ASSOC_IND: |
| stat = ST(ASSOC_REQ); |
| break; |
| default: |
| break; |
| } |
| |
| /* logging interested events */ |
| if (DHD_STATLOG_VALID(stat)) { |
| dhd_statlog_ring_log(dhdp, stat, ifidx, dir, status, reason); |
| } |
| |
| return BCME_OK; |
| } |
| |
| uint32 |
| dhd_statlog_get_logbuf_len(dhd_pub_t *dhdp) |
| { |
| uint32 length = 0; |
| dhd_statlog_t *statlog; |
| |
| if (dhdp && dhdp->statlog) { |
| statlog = (dhd_statlog_t *)(dhdp->statlog); |
| length = statlog->logbuf_len; |
| } |
| |
| return length; |
| } |
| |
| void * |
| dhd_statlog_get_logbuf(dhd_pub_t *dhdp) |
| { |
| dhd_statlog_t *statlog; |
| void *ret_addr = NULL; |
| |
| if (dhdp && dhdp->statlog) { |
| statlog = (dhd_statlog_t *)(dhdp->statlog); |
| ret_addr = (void *)(statlog->logbuf); |
| } |
| |
| return ret_addr; |
| } |
| |
| /* |
| * called function uses buflen as the DHD_STATLOG_STATSTR_BUF_LEN max. |
| * So when adding a case, make sure the string is less than |
| * the DHD_STATLOG_STATSTR_BUF_LEN bytes |
| */ |
| static void |
| dhd_statlog_stat_name(char *buf, uint32 buflen, uint32 state, uint8 dir) |
| { |
| char *stat_str = NULL; |
| bool tx = (dir == STDIR(TX)); |
| uint32 max_buf_len = MIN(buflen, DHD_STATLOG_STATSTR_BUF_LEN); |
| |
| switch (state) { |
| case ST(INVALID): |
| stat_str = "INVALID_STATE"; |
| break; |
| case ST(WLAN_POWER_ON): |
| stat_str = "WLAN_POWER_ON"; |
| break; |
| case ST(WLAN_POWER_OFF): |
| stat_str = "WLAN_POWER_OFF"; |
| break; |
| case ST(ASSOC_START): |
| stat_str = "ASSOC_START"; |
| break; |
| case ST(AUTH_DONE): |
| stat_str = "AUTH_DONE"; |
| break; |
| case ST(ASSOC_REQ): |
| stat_str = tx ? "ASSOC_REQ" : "RX_ASSOC_REQ"; |
| break; |
| case ST(ASSOC_RESP): |
| stat_str = "ASSOC_RESP"; |
| break; |
| case ST(ASSOC_DONE): |
| stat_str = "ASSOC_DONE"; |
| break; |
| case ST(DISASSOC_START): |
| stat_str = "DISASSOC_START"; |
| break; |
| case ST(DISASSOC_INT_START): |
| stat_str = "DISASSOC_INTERNAL_START"; |
| break; |
| case ST(DISASSOC_DONE): |
| stat_str = "DISASSOC_DONE"; |
| break; |
| case ST(DISASSOC): |
| stat_str = tx ? "DISASSOC_EVENT" : "DISASSOC_IND_EVENT"; |
| break; |
| case ST(DEAUTH): |
| stat_str = tx ? "DEAUTH_EVENT" : "DEAUTH_IND_EVENT"; |
| break; |
| case ST(LINKDOWN): |
| stat_str = "LINKDOWN_EVENT"; |
| break; |
| case ST(REASSOC_START): |
| stat_str = "REASSOC_START"; |
| break; |
| case ST(REASSOC_INFORM): |
| stat_str = "REASSOC_INFORM"; |
| break; |
| case ST(REASSOC_DONE): |
| stat_str = "REASSOC_DONE_SUCCESS"; |
| break; |
| case ST(EAPOL_M1): |
| stat_str = tx ? "TX_EAPOL_M1" : "RX_EAPOL_M1"; |
| break; |
| case ST(EAPOL_M2): |
| stat_str = tx ? "TX_EAPOL_M2" : "RX_EAPOL_M2"; |
| break; |
| case ST(EAPOL_M3): |
| stat_str = tx ? "TX_EAPOL_M3" : "RX_EAPOL_M3"; |
| break; |
| case ST(EAPOL_M4): |
| stat_str = tx ? "TX_EAPOL_M4" : "RX_EAPOL_M4"; |
| break; |
| case ST(EAPOL_GROUPKEY_M1): |
| stat_str = tx ? "TX_EAPOL_GROUPKEY_M1" : "RX_EAPOL_GROUPKEY_M1"; |
| break; |
| case ST(EAPOL_GROUPKEY_M2): |
| stat_str = tx ? "TX_EAPOL_GROUPKEY_M2" : "RX_EAPOL_GROUPKEY_M2"; |
| break; |
| case ST(EAP_REQ_IDENTITY): |
| stat_str = tx ? "TX_EAP_REQ_IDENTITY" : "RX_EAP_REQ_IDENTITY"; |
| break; |
| case ST(EAP_RESP_IDENTITY): |
| stat_str = tx ? "TX_EAP_RESP_IDENTITY" : "RX_EAP_RESP_IDENTITY"; |
| break; |
| case ST(EAP_REQ_TLS): |
| stat_str = tx ? "TX_EAP_REQ_TLS" : "RX_EAP_REQ_TLS"; |
| break; |
| case ST(EAP_RESP_TLS): |
| stat_str = tx ? "TX_EAP_RESP_TLS" : "RX_EAP_RESP_TLS"; |
| break; |
| case ST(EAP_REQ_LEAP): |
| stat_str = tx ? "TX_EAP_REQ_LEAP" : "RX_EAP_REQ_LEAP"; |
| break; |
| case ST(EAP_RESP_LEAP): |
| stat_str = tx ? "TX_EAP_RESP_LEAP" : "RX_EAP_RESP_LEAP"; |
| break; |
| case ST(EAP_REQ_TTLS): |
| stat_str = tx ? "TX_EAP_REQ_TTLS" : "RX_EAP_REQ_TTLS"; |
| break; |
| case ST(EAP_RESP_TTLS): |
| stat_str = tx ? "TX_EAP_RESP_TTLS" : "RX_EAP_RESP_TTLS"; |
| break; |
| case ST(EAP_REQ_AKA): |
| stat_str = tx ? "TX_EAP_REQ_AKA" : "RX_EAP_REQ_AKA"; |
| break; |
| case ST(EAP_RESP_AKA): |
| stat_str = tx ? "TX_EAP_RESP_AKA" : "RX_EAP_RESP_AKA"; |
| break; |
| case ST(EAP_REQ_PEAP): |
| stat_str = tx ? "TX_EAP_REQ_PEAP" : "RX_EAP_REQ_PEAP"; |
| break; |
| case ST(EAP_RESP_PEAP): |
| stat_str = tx ? "TX_EAP_RESP_PEAP" : "RX_EAP_RESP_PEAP"; |
| break; |
| case ST(EAP_REQ_FAST): |
| stat_str = tx ? "TX_EAP_REQ_FAST" : "RX_EAP_REQ_FAST"; |
| break; |
| case ST(EAP_RESP_FAST): |
| stat_str = tx ? "TX_EAP_RESP_FAST" : "RX_EAP_RESP_FAST"; |
| break; |
| case ST(EAP_REQ_PSK): |
| stat_str = tx ? "TX_EAP_REQ_PSK" : "RX_EAP_REQ_PSK"; |
| break; |
| case ST(EAP_RESP_PSK): |
| stat_str = tx ? "TX_EAP_RESP_PSK" : "RX_EAP_RESP_PSK"; |
| break; |
| case ST(EAP_REQ_AKAP): |
| stat_str = tx ? "TX_EAP_REQ_AKAP" : "RX_EAP_REQ_AKAP"; |
| break; |
| case ST(EAP_RESP_AKAP): |
| stat_str = tx ? "TX_EAP_RESP_AKAP" : "RX_EAP_RESP_AKAP"; |
| break; |
| case ST(EAP_SUCCESS): |
| stat_str = tx ? "TX_EAP_SUCCESS" : "RX_EAP_SUCCESS"; |
| break; |
| case ST(EAP_FAILURE): |
| stat_str = tx ? "TX_EAP_FAILURE" : "RX_EAP_FAILURE"; |
| break; |
| case ST(EAPOL_START): |
| stat_str = tx ? "TX_EAPOL_START" : "RX_EAPOL_START"; |
| break; |
| case ST(WSC_START): |
| stat_str = tx ? "TX_WSC_START" : "RX_WSC_START"; |
| break; |
| case ST(WSC_DONE): |
| stat_str = tx ? "TX_WSC_DONE" : "RX_WSC_DONE"; |
| break; |
| case ST(WPS_M1): |
| stat_str = tx ? "TX_WPS_M1" : "RX_WPS_M1"; |
| break; |
| case ST(WPS_M2): |
| stat_str = tx ? "TX_WPS_M2" : "RX_WPS_M2"; |
| break; |
| case ST(WPS_M3): |
| stat_str = tx ? "TX_WPS_M3" : "RX_WPS_M3"; |
| break; |
| case ST(WPS_M4): |
| stat_str = tx ? "TX_WPS_M4" : "RX_WPS_M4"; |
| break; |
| case ST(WPS_M5): |
| stat_str = tx ? "TX_WPS_M5" : "RX_WPS_M5"; |
| break; |
| case ST(WPS_M6): |
| stat_str = tx ? "TX_WPS_M6" : "RX_WPS_M6"; |
| break; |
| case ST(WPS_M7): |
| stat_str = tx ? "TX_WPS_M7" : "RX_WPS_M7"; |
| break; |
| case ST(WPS_M8): |
| stat_str = tx ? "TX_WPS_M8" : "RX_WPS_M8"; |
| break; |
| case ST(8021X_OTHER): |
| stat_str = tx ? "TX_OTHER_8021X" : "RX_OTHER_8021X"; |
| break; |
| case ST(INSTALL_KEY): |
| stat_str = "INSTALL_KEY"; |
| break; |
| case ST(DELETE_KEY): |
| stat_str = "DELETE_KEY"; |
| break; |
| case ST(INSTALL_PMKSA): |
| stat_str = "INSTALL_PMKSA"; |
| break; |
| case ST(INSTALL_OKC_PMK): |
| stat_str = "INSTALL_OKC_PMK"; |
| break; |
| case ST(DHCP_DISCOVER): |
| stat_str = tx ? "TX_DHCP_DISCOVER" : "RX_DHCP_DISCOVER"; |
| break; |
| case ST(DHCP_OFFER): |
| stat_str = tx ? "TX_DHCP_OFFER" : "RX_DHCP_OFFER"; |
| break; |
| case ST(DHCP_REQUEST): |
| stat_str = tx ? "TX_DHCP_REQUEST" : "RX_DHCP_REQUEST"; |
| break; |
| case ST(DHCP_DECLINE): |
| stat_str = tx ? "TX_DHCP_DECLINE" : "RX_DHCP_DECLINE"; |
| break; |
| case ST(DHCP_ACK): |
| stat_str = tx ? "TX_DHCP_ACK" : "RX_DHCP_ACK"; |
| break; |
| case ST(DHCP_NAK): |
| stat_str = tx ? "TX_DHCP_NAK" : "RX_DHCP_NAK"; |
| break; |
| case ST(DHCP_RELEASE): |
| stat_str = tx ? "TX_DHCP_RELEASE" : "RX_DHCP_RELEASE"; |
| break; |
| case ST(DHCP_INFORM): |
| stat_str = tx ? "TX_DHCP_INFORM" : "RX_DHCP_INFORM"; |
| break; |
| case ST(ICMP_PING_REQ): |
| stat_str = tx ? "TX_ICMP_PING_REQ" : "RX_ICMP_PING_REQ"; |
| break; |
| case ST(ICMP_PING_RESP): |
| stat_str = tx ? "TX_ICMP_PING_RESP" : "RX_ICMP_PING_RESP"; |
| break; |
| case ST(ICMP_DEST_UNREACH): |
| stat_str = tx ? "TX_ICMP_DEST_UNREACH" : "RX_ICMP_DEST_UNREACH"; |
| break; |
| case ST(ICMP_OTHER): |
| stat_str = tx ? "TX_ICMP_OTHER" : "RX_ICMP_OTHER"; |
| break; |
| case ST(ARP_REQ): |
| stat_str = tx ? "TX_ARP_REQ" : "RX_ARP_REQ"; |
| break; |
| case ST(ARP_RESP): |
| stat_str = tx ? "TX_ARP_RESP" : "RX_ARP_RESP"; |
| break; |
| case ST(DNS_QUERY): |
| stat_str = tx ? "TX_DNS_QUERY" : "RX_DNS_QUERY"; |
| break; |
| case ST(DNS_RESP): |
| stat_str = tx ? "TX_DNS_RESP" : "RX_DNS_RESP"; |
| break; |
| case ST(REASSOC_SUCCESS): |
| stat_str = "REASSOC_SUCCESS"; |
| break; |
| case ST(REASSOC_FAILURE): |
| stat_str = "REASSOC_FAILURE"; |
| break; |
| case ST(AUTH_TIMEOUT): |
| stat_str = "AUTH_TIMEOUT"; |
| break; |
| case ST(AUTH_FAIL): |
| stat_str = "AUTH_FAIL"; |
| break; |
| case ST(AUTH_NO_ACK): |
| stat_str = "AUTH_NO_ACK"; |
| break; |
| case ST(AUTH_OTHERS): |
| stat_str = "AUTH_FAIL_OTHER_STATUS"; |
| break; |
| case ST(ASSOC_TIMEOUT): |
| stat_str = "ASSOC_TIMEOUT"; |
| break; |
| case ST(ASSOC_FAIL): |
| stat_str = "ASSOC_FAIL"; |
| break; |
| case ST(ASSOC_NO_ACK): |
| stat_str = "ASSOC_NO_ACK"; |
| break; |
| case ST(ASSOC_ABORT): |
| stat_str = "ASSOC_ABORT"; |
| break; |
| case ST(ASSOC_UNSOLICITED): |
| stat_str = "ASSOC_UNSOLICITED"; |
| break; |
| case ST(ASSOC_NO_NETWORKS): |
| stat_str = "ASSOC_NO_NETWORKS"; |
| break; |
| case ST(ASSOC_OTHERS): |
| stat_str = "ASSOC_FAIL_OTHER_STATUS"; |
| break; |
| case ST(REASSOC_DONE_OTHERS): |
| stat_str = "REASSOC_DONE_OTHER_STATUS"; |
| break; |
| default: |
| stat_str = "UNKNOWN_STATUS"; |
| break; |
| } |
| |
| strncpy(buf, stat_str, max_buf_len); |
| buf[max_buf_len - 1] = '\0'; |
| } |
| |
| static void |
| dhd_statlog_get_timestamp(stat_elem_t *elem, uint64 *sec, uint64 *usec) |
| { |
| uint64 ts_nsec, rem_nsec; |
| |
| ts_nsec = elem->ts; |
| rem_nsec = DIV_AND_MOD_U64_BY_U32(ts_nsec, NSEC_PER_SEC); |
| *sec = ts_nsec; |
| *usec = (uint64)(rem_nsec / NSEC_PER_USEC); |
| } |
| |
| static void |
| dhd_statlog_convert_time(stat_elem_t *elem, uint8 *buf, uint32 buflen) |
| { |
| struct rtc_time tm; |
| uint64 ts_sec, rem_usec; |
| |
| if (!buf) { |
| DHD_STATLOG_ERR(("%s: buf is NULL\n", __FUNCTION__)); |
| return; |
| } |
| |
| bzero(buf, buflen); |
| ts_sec = elem->ts_tz; |
| rem_usec = DIV_AND_MOD_U64_BY_U32(ts_sec, USEC_PER_SEC); |
| |
| rtc_time_to_tm((unsigned long)ts_sec, &tm); |
| snprintf(buf, buflen, DHD_STATLOG_TZFMT_YYMMDDHHMMSSMS, |
| tm.tm_year - 100, tm.tm_mon + 1, tm.tm_mday, |
| tm.tm_hour, tm.tm_min, tm.tm_sec, |
| (uint32)(rem_usec / USEC_PER_MSEC)); |
| } |
| |
| #ifdef DHD_LOG_DUMP |
| static int |
| dhd_statlog_dump(dhd_statlog_t *statlog, char *buf, uint32 buflen) |
| { |
| stat_elem_t *elem; |
| struct bcmstrbuf b; |
| struct bcmstrbuf *strbuf = &b; |
| char stat_str[DHD_STATLOG_STATSTR_BUF_LEN]; |
| char ts_str[DHD_STATLOG_TZFMT_BUF_LEN]; |
| uint64 sec = 0, usec = 0; |
| |
| if (!statlog) { |
| DHD_STATLOG_ERR(("%s: statlog is NULL\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| bcm_binit(strbuf, buf, buflen); |
| bzero(stat_str, sizeof(stat_str)); |
| bzero(ts_str, sizeof(ts_str)); |
| dhd_ring_whole_lock(statlog->ringbuf); |
| elem = (stat_elem_t *)dhd_ring_get_first(statlog->ringbuf); |
| while (elem) { |
| if (DHD_STATLOG_VALID(elem->stat)) { |
| dhd_statlog_stat_name(stat_str, sizeof(stat_str), |
| elem->stat, elem->dir); |
| dhd_statlog_get_timestamp(elem, &sec, &usec); |
| dhd_statlog_convert_time(elem, ts_str, sizeof(ts_str)); |
| bcm_bprintf(strbuf, "[%s][%5lu.%06lu] status=%s, ifidx=%d, " |
| "reason=%d, status=%d\n", ts_str, (unsigned long)sec, |
| (unsigned long)usec, stat_str, elem->ifidx, |
| elem->reason, elem->status); |
| } |
| elem = (stat_elem_t *)dhd_ring_get_next(statlog->ringbuf, (void *)elem); |
| } |
| dhd_ring_whole_unlock(statlog->ringbuf); |
| |
| return (!strbuf->size ? BCME_BUFTOOSHORT : strbuf->size); |
| } |
| |
| int |
| dhd_statlog_write_logdump(dhd_pub_t *dhdp, const void *user_buf, |
| void *fp, uint32 len, unsigned long *pos) |
| { |
| dhd_statlog_t *statlog; |
| log_dump_section_hdr_t sec_hdr; |
| char *buf; |
| uint32 buflen; |
| int remain_len = 0; |
| int ret = BCME_OK; |
| |
| if (!dhdp || !dhdp->statlog) { |
| DHD_STATLOG_ERR(("%s: dhdp or dhdp->statlog is NULL\n", |
| __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| statlog = (dhd_statlog_t *)(dhdp->statlog); |
| if (!statlog->logbuf) { |
| DHD_STATLOG_ERR(("%s: logbuf is NULL\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| buf = statlog->logbuf; |
| buflen = statlog->logbuf_len; |
| bzero(buf, buflen); |
| |
| remain_len = dhd_statlog_dump(statlog, buf, buflen); |
| if (remain_len < 0) { |
| DHD_STATLOG_ERR(("%s: failed to write stat info to buffer\n", |
| __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| DHD_STATLOG_INFO(("%s: Start to write statlog\n", __FUNCTION__)); |
| |
| /* write the section header first */ |
| ret = dhd_export_debug_data(STATUS_LOG_HDR, fp, user_buf, |
| strlen(STATUS_LOG_HDR), pos); |
| if (ret < 0) { |
| goto exit; |
| } |
| |
| dhd_init_sec_hdr(&sec_hdr); |
| sec_hdr.type = LOG_DUMP_SECTION_STATUS; |
| sec_hdr.length = buflen - remain_len; |
| ret = dhd_export_debug_data((char *)&sec_hdr, fp, user_buf, |
| sizeof(sec_hdr), pos); |
| if (ret < 0) { |
| goto exit; |
| } |
| |
| /* write status log info */ |
| ret = dhd_export_debug_data(buf, fp, user_buf, buflen - remain_len, pos); |
| if (ret < 0) { |
| DHD_STATLOG_ERR(("%s: failed to write stat info, err=%d\n", |
| __FUNCTION__, ret)); |
| } |
| |
| DHD_STATLOG_INFO(("%s: Complete to write statlog file, err=%d\n", |
| __FUNCTION__, ret)); |
| |
| exit: |
| return ret; |
| } |
| #endif /* DHD_LOG_DUMP */ |
| |
| int |
| dhd_statlog_generate_bdmask(dhd_pub_t *dhdp, void *reqbuf) |
| { |
| dhd_statlog_t *statlog; |
| stat_bdmask_req_t *query; |
| uint8 *req_buf; |
| uint32 req_buf_len; |
| int cnt; |
| |
| if (!dhdp || !dhdp->statlog) { |
| DHD_STATLOG_ERR(("%s: dhdp or statlog is NULL\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| if (!reqbuf) { |
| DHD_STATLOG_ERR(("%s: invalid query\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| statlog = dhdp->statlog; |
| query = (stat_bdmask_req_t *)reqbuf; |
| req_buf = query->req_buf; |
| req_buf_len = query->req_buf_len; |
| if (!req_buf) { |
| DHD_STATLOG_ERR(("%s: invalid query\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| bzero(statlog->bdmask, DHD_STAT_BDMASK_SIZE); |
| for (cnt = 0; cnt < req_buf_len; cnt++) { |
| if (DHD_STATLOG_VALID(req_buf[cnt])) { |
| setbit(statlog->bdmask, req_buf[cnt]); |
| } |
| } |
| |
| return BCME_OK; |
| } |
| |
| int |
| dhd_statlog_get_latest_info(dhd_pub_t *dhdp, void *reqbuf) |
| { |
| dhd_statlog_t *statlog; |
| stat_query_t *query; |
| stat_elem_t *elem; |
| uint8 *req_buf, *resp_buf, *sp; |
| uint32 req_buf_len, resp_buf_len, req_num; |
| int i, remain_len, cpcnt = 0; |
| uint8 filter[DHD_STAT_BDMASK_SIZE]; |
| bool query_bigdata = FALSE; |
| void *ringbuf; |
| |
| if (!dhdp || !dhdp->statlog) { |
| DHD_STATLOG_ERR(("%s: dhdp or statlog is NULL\n", |
| __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| query = (stat_query_t *)reqbuf; |
| if (!query) { |
| DHD_STATLOG_ERR(("%s: invalid query\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| statlog = (dhd_statlog_t *)(dhdp->statlog); |
| req_buf = query->req_buf; |
| req_buf_len = query->req_buf_len; |
| resp_buf = query->resp_buf; |
| resp_buf_len = query->resp_buf_len; |
| req_num = MIN(query->req_num, MAX_STATLOG_REQ_ITEM); |
| if (!resp_buf) { |
| DHD_STATLOG_ERR(("%s: invalid query\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| bzero(filter, sizeof(filter)); |
| if (!req_buf || !req_buf_len) { |
| query_bigdata = TRUE; |
| ringbuf = statlog->bdlog_ringbuf; |
| } else { |
| ringbuf = statlog->ringbuf; |
| /* build a filter from req_buf */ |
| for (i = 0; i < req_buf_len; i++) { |
| if (DHD_STATLOG_VALID(req_buf[i])) { |
| setbit(filter, req_buf[i]); |
| } |
| } |
| } |
| |
| sp = resp_buf; |
| remain_len = resp_buf_len; |
| dhd_ring_whole_lock(ringbuf); |
| elem = (stat_elem_t *)dhd_ring_get_last(ringbuf); |
| while (elem) { |
| if (query_bigdata || isset(filter, elem->stat)) { |
| /* found the status from the list of interests */ |
| if (remain_len < sizeof(stat_elem_t)) { |
| dhd_ring_whole_unlock(ringbuf); |
| return BCME_BUFTOOSHORT; |
| } |
| bcopy((char *)elem, sp, sizeof(stat_elem_t)); |
| sp += sizeof(stat_elem_t); |
| remain_len -= sizeof(stat_elem_t); |
| cpcnt++; |
| } |
| |
| if (cpcnt >= req_num) { |
| break; |
| } |
| |
| /* Proceed to next item */ |
| elem = (stat_elem_t *)dhd_ring_get_prev(ringbuf, (void *)elem); |
| } |
| dhd_ring_whole_unlock(ringbuf); |
| |
| return cpcnt; |
| } |
| |
| int |
| dhd_statlog_query(dhd_pub_t *dhdp, char *cmd, int total_len) |
| { |
| stat_elem_t *elem = NULL; |
| stat_query_t query; |
| char *pos, *token; |
| uint8 *req_buf = NULL, *resp_buf = NULL; |
| uint32 req_buf_len = 0, resp_buf_len = 0; |
| ulong req_num, stat_num, stat; |
| char stat_str[DHD_STATLOG_STATSTR_BUF_LEN]; |
| uint64 sec = 0, usec = 0; |
| int i, resp_num, err = BCME_OK; |
| char ts_str[DHD_STATLOG_TZFMT_BUF_LEN]; |
| |
| /* |
| * DRIVER QUERY_STAT_LOG <total req num> <stat list num> <stat list> |
| * Note: use the defult status list if the 'stat list num' is zero |
| */ |
| pos = cmd; |
| /* drop command */ |
| token = bcmstrtok(&pos, " ", NULL); |
| /* total number of request */ |
| token = bcmstrtok(&pos, " ", NULL); |
| if (!token) { |
| err = BCME_BADARG; |
| goto exit; |
| } |
| |
| req_num = bcm_strtoul(token, NULL, 0); |
| |
| /* total number of status list */ |
| token = bcmstrtok(&pos, " ", NULL); |
| if (!token) { |
| err = BCME_BADARG; |
| goto exit; |
| } |
| |
| stat_num = bcm_strtoul(token, NULL, 0); |
| if (stat_num) { |
| /* create a status list */ |
| req_buf_len = (uint32)(stat_num * sizeof(uint8)); |
| req_buf = (uint8 *)MALLOCZ(dhdp->osh, req_buf_len); |
| if (!req_buf) { |
| DHD_STATLOG_ERR(("%s: failed to allocate request buf\n", |
| __FUNCTION__)); |
| err = BCME_NOMEM; |
| goto exit; |
| } |
| |
| /* parse the status list and update to the request buffer */ |
| for (i = 0; i < (uint32)stat_num; i++) { |
| token = bcmstrtok(&pos, " ", NULL); |
| if (!token) { |
| err = BCME_BADARG; |
| goto exit; |
| } |
| stat = bcm_strtoul(token, NULL, 0); |
| req_buf[i] = (uint8)stat; |
| } |
| } |
| |
| /* creat a response list */ |
| resp_buf_len = (uint32)DHD_STATLOG_RING_SIZE(req_num); |
| resp_buf = (uint8 *)MALLOCZ(dhdp->osh, resp_buf_len); |
| if (!resp_buf) { |
| DHD_STATLOG_ERR(("%s: failed to allocate response buf\n", |
| __FUNCTION__)); |
| err = BCME_NOMEM; |
| goto exit; |
| } |
| |
| /* create query format and query the status */ |
| query.req_buf = req_buf; |
| query.req_buf_len = req_buf_len; |
| query.resp_buf = resp_buf; |
| query.resp_buf_len = resp_buf_len; |
| query.req_num = (uint32)req_num; |
| resp_num = dhd_statlog_get_latest_info(dhdp, (void *)&query); |
| if (resp_num < 0) { |
| DHD_STATLOG_ERR(("%s: failed to query the status\n", __FUNCTION__)); |
| err = BCME_ERROR; |
| goto exit; |
| } |
| |
| /* print out the results */ |
| DHD_STATLOG_PRINT(("=============== QUERY RESULT ===============\n")); |
| if (resp_num > 0) { |
| elem = (stat_elem_t *)resp_buf; |
| for (i = 0; i < resp_num; i++) { |
| if (DHD_STATLOG_VALID(elem->stat)) { |
| dhd_statlog_stat_name(stat_str, sizeof(stat_str), |
| elem->stat, elem->dir); |
| dhd_statlog_get_timestamp(elem, &sec, &usec); |
| dhd_statlog_convert_time(elem, ts_str, sizeof(ts_str)); |
| DHD_STATLOG_PRINT(("[RAWTS:%llu][%s][%5lu.%06lu] status=%s," |
| " ifidx=%d, reason=%d, status=%d\n", elem->ts_tz, |
| ts_str, (unsigned long)sec, (unsigned long)usec, |
| stat_str, elem->ifidx, elem->reason, elem->status)); |
| } |
| elem++; |
| } |
| } else { |
| DHD_STATLOG_PRINT(("No data found\n")); |
| } |
| |
| exit: |
| if (resp_buf) { |
| MFREE(dhdp->osh, resp_buf, resp_buf_len); |
| } |
| |
| if (req_buf) { |
| MFREE(dhdp->osh, req_buf, req_buf_len); |
| } |
| |
| return err; |
| } |
| |
| void |
| dhd_statlog_dump_scr(dhd_pub_t *dhdp) |
| { |
| dhd_statlog_t *statlog; |
| stat_elem_t *elem; |
| char stat_str[DHD_STATLOG_STATSTR_BUF_LEN]; |
| char ts_str[DHD_STATLOG_TZFMT_BUF_LEN]; |
| uint64 sec = 0, usec = 0; |
| |
| if (!dhdp || !dhdp->statlog) { |
| DHD_STATLOG_ERR(("%s: dhdp or statlog is NULL\n", __FUNCTION__)); |
| return; |
| } |
| |
| statlog = (dhd_statlog_t *)(dhdp->statlog); |
| bzero(stat_str, sizeof(stat_str)); |
| bzero(ts_str, sizeof(ts_str)); |
| |
| DHD_STATLOG_PRINT(("=============== START OF CURRENT STATUS INFO ===============\n")); |
| dhd_ring_whole_lock(statlog->ringbuf); |
| elem = (stat_elem_t *)dhd_ring_get_first(statlog->ringbuf); |
| while (elem) { |
| if (DHD_STATLOG_VALID(elem->stat)) { |
| dhd_statlog_stat_name(stat_str, sizeof(stat_str), |
| elem->stat, elem->dir); |
| dhd_statlog_get_timestamp(elem, &sec, &usec); |
| dhd_statlog_convert_time(elem, ts_str, sizeof(ts_str)); |
| DHD_STATLOG_PRINT(("[RAWTS:%llu][%s][%5lu.%06lu] status=%s," |
| " ifidx=%d, reason=%d, status=%d\n", elem->ts_tz, ts_str, |
| (unsigned long)sec, (unsigned long)usec, stat_str, |
| elem->ifidx, elem->reason, elem->status)); |
| } |
| elem = (stat_elem_t *)dhd_ring_get_next(statlog->ringbuf, (void *)elem); |
| } |
| dhd_ring_whole_unlock(statlog->ringbuf); |
| DHD_STATLOG_PRINT(("=============== END OF CURRENT STATUS INFO ===============\n")); |
| } |
| #endif /* DHD_STATUS_LOGGING */ |