| /* |
| * DHD debugability support |
| * |
| * <<Broadcom-WL-IPTag/Open:>> |
| * |
| * Copyright (C) 1999-2017, Broadcom Corporation |
| * |
| * 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. |
| * |
| * Notwithstanding the above, under no circumstances may you combine this |
| * software in any way with any other Broadcom software provided under a license |
| * other than the GPL, without Broadcom's express prior written consent. |
| * |
| * $Id: dhd_debug.c 711908 2017-07-20 10:37:34Z $ |
| */ |
| |
| #include <typedefs.h> |
| #include <osl.h> |
| #include <bcmutils.h> |
| #include <bcmendian.h> |
| #include <dngl_stats.h> |
| #include <dhd.h> |
| #include <dhd_dbg.h> |
| #include <dhd_debug.h> |
| #include <dhd_mschdbg.h> |
| |
| #include <event_log.h> |
| #include <event_trace.h> |
| #include <msgtrace.h> |
| |
| #if defined(DHD_EFI) |
| #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) |
| #define container_of(ptr, type, member) \ |
| ((type *)((char *)(ptr) - offsetof(type, member))) |
| #endif |
| |
| #define DBGRING_FLUSH_THRESHOLD(ring) (ring->ring_size / 3) |
| #define RING_STAT_TO_STATUS(ring, status) \ |
| do { \ |
| strncpy(status.name, ring->name, \ |
| sizeof(status.name) - 1); \ |
| status.ring_id = ring->id; \ |
| status.ring_buffer_byte_size = ring->ring_size; \ |
| status.written_bytes = ring->stat.written_bytes; \ |
| status.written_records = ring->stat.written_records; \ |
| status.read_bytes = ring->stat.read_bytes; \ |
| status.verbose_level = ring->log_level; \ |
| } while (0) |
| |
| #define DHD_PKT_INFO DHD_ERROR |
| struct map_table { |
| uint16 fw_id; |
| uint16 host_id; |
| char *desc; |
| }; |
| |
| struct map_table event_map[] = { |
| {WLC_E_AUTH, WIFI_EVENT_AUTH_COMPLETE, "AUTH_COMPLETE"}, |
| {WLC_E_ASSOC, WIFI_EVENT_ASSOC_COMPLETE, "ASSOC_COMPLETE"}, |
| {TRACE_FW_AUTH_STARTED, WIFI_EVENT_FW_AUTH_STARTED, "AUTH STARTED"}, |
| {TRACE_FW_ASSOC_STARTED, WIFI_EVENT_FW_ASSOC_STARTED, "ASSOC STARTED"}, |
| {TRACE_FW_RE_ASSOC_STARTED, WIFI_EVENT_FW_RE_ASSOC_STARTED, "REASSOC STARTED"}, |
| {TRACE_G_SCAN_STARTED, WIFI_EVENT_G_SCAN_STARTED, "GSCAN STARTED"}, |
| {WLC_E_PFN_SCAN_COMPLETE, WIFI_EVENT_G_SCAN_COMPLETE, "GSCAN COMPLETE"}, |
| {WLC_E_DISASSOC, WIFI_EVENT_DISASSOCIATION_REQUESTED, "DIASSOC REQUESTED"}, |
| {WLC_E_REASSOC, WIFI_EVENT_RE_ASSOCIATION_REQUESTED, "REASSOC REQUESTED"}, |
| {TRACE_ROAM_SCAN_STARTED, WIFI_EVENT_ROAM_REQUESTED, "ROAM REQUESTED"}, |
| {WLC_E_BEACON_FRAME_RX, WIFI_EVENT_BEACON_RECEIVED, "BEACON Received"}, |
| {TRACE_ROAM_SCAN_STARTED, WIFI_EVENT_ROAM_SCAN_STARTED, "ROAM SCAN STARTED"}, |
| {TRACE_ROAM_SCAN_COMPLETE, WIFI_EVENT_ROAM_SCAN_COMPLETE, "ROAM SCAN COMPLETED"}, |
| {TRACE_ROAM_AUTH_STARTED, WIFI_EVENT_ROAM_AUTH_STARTED, "ROAM AUTH STARTED"}, |
| {WLC_E_AUTH, WIFI_EVENT_ROAM_AUTH_COMPLETE, "ROAM AUTH COMPLETED"}, |
| {TRACE_FW_RE_ASSOC_STARTED, WIFI_EVENT_ROAM_ASSOC_STARTED, "ROAM ASSOC STARTED"}, |
| {WLC_E_ASSOC, WIFI_EVENT_ROAM_ASSOC_COMPLETE, "ROAM ASSOC COMPLETED"}, |
| {TRACE_ROAM_SCAN_COMPLETE, WIFI_EVENT_ROAM_SCAN_COMPLETE, "ROAM SCAN COMPLETED"}, |
| {TRACE_BT_COEX_BT_SCO_START, WIFI_EVENT_BT_COEX_BT_SCO_START, "BT SCO START"}, |
| {TRACE_BT_COEX_BT_SCO_STOP, WIFI_EVENT_BT_COEX_BT_SCO_STOP, "BT SCO STOP"}, |
| {TRACE_BT_COEX_BT_SCAN_START, WIFI_EVENT_BT_COEX_BT_SCAN_START, "BT COEX SCAN START"}, |
| {TRACE_BT_COEX_BT_SCAN_STOP, WIFI_EVENT_BT_COEX_BT_SCAN_STOP, "BT COEX SCAN STOP"}, |
| {TRACE_BT_COEX_BT_HID_START, WIFI_EVENT_BT_COEX_BT_HID_START, "BT HID START"}, |
| {TRACE_BT_COEX_BT_HID_STOP, WIFI_EVENT_BT_COEX_BT_HID_STOP, "BT HID STOP"}, |
| {WLC_E_EAPOL_MSG, WIFI_EVENT_FW_EAPOL_FRAME_RECEIVED, "FW EAPOL PKT RECEIVED"}, |
| {TRACE_FW_EAPOL_FRAME_TRANSMIT_START, WIFI_EVENT_FW_EAPOL_FRAME_TRANSMIT_START, |
| "FW EAPOL PKT TRANSMITED"}, |
| {TRACE_FW_EAPOL_FRAME_TRANSMIT_STOP, WIFI_EVENT_FW_EAPOL_FRAME_TRANSMIT_STOP, |
| "FW EAPOL PKT TX STOPPED"}, |
| {TRACE_BLOCK_ACK_NEGOTIATION_COMPLETE, WIFI_EVENT_BLOCK_ACK_NEGOTIATION_COMPLETE, |
| "BLOCK ACK NEGO COMPLETED"}, |
| }; |
| |
| struct map_table event_tag_map[] = { |
| {TRACE_TAG_VENDOR_SPECIFIC, WIFI_TAG_VENDOR_SPECIFIC, "VENDOR SPECIFIC DATA"}, |
| {TRACE_TAG_BSSID, WIFI_TAG_BSSID, "BSSID"}, |
| {TRACE_TAG_ADDR, WIFI_TAG_ADDR, "ADDR_0"}, |
| {TRACE_TAG_SSID, WIFI_TAG_SSID, "SSID"}, |
| {TRACE_TAG_STATUS, WIFI_TAG_STATUS, "STATUS"}, |
| {TRACE_TAG_CHANNEL_SPEC, WIFI_TAG_CHANNEL_SPEC, "CHANSPEC"}, |
| {TRACE_TAG_WAKE_LOCK_EVENT, WIFI_TAG_WAKE_LOCK_EVENT, "WAKELOCK EVENT"}, |
| {TRACE_TAG_ADDR1, WIFI_TAG_ADDR1, "ADDR_1"}, |
| {TRACE_TAG_ADDR2, WIFI_TAG_ADDR2, "ADDR_2"}, |
| {TRACE_TAG_ADDR3, WIFI_TAG_ADDR3, "ADDR_3"}, |
| {TRACE_TAG_ADDR4, WIFI_TAG_ADDR4, "ADDR_4"}, |
| {TRACE_TAG_TSF, WIFI_TAG_TSF, "TSF"}, |
| {TRACE_TAG_IE, WIFI_TAG_IE, "802.11 IE"}, |
| {TRACE_TAG_INTERFACE, WIFI_TAG_INTERFACE, "INTERFACE"}, |
| {TRACE_TAG_REASON_CODE, WIFI_TAG_REASON_CODE, "REASON CODE"}, |
| {TRACE_TAG_RATE_MBPS, WIFI_TAG_RATE_MBPS, "RATE"}, |
| }; |
| |
| /* define log level per ring type */ |
| struct log_level_table fw_verbose_level_map[] = { |
| {1, EVENT_LOG_TAG_PCI_ERROR, EVENT_LOG_SET_BUS, "PCI_ERROR"}, |
| {1, EVENT_LOG_TAG_PCI_WARN, EVENT_LOG_SET_BUS, "PCI_WARN"}, |
| {2, EVENT_LOG_TAG_PCI_INFO, EVENT_LOG_SET_BUS, "PCI_INFO"}, |
| {3, EVENT_LOG_TAG_PCI_DBG, EVENT_LOG_SET_BUS, "PCI_DEBUG"}, |
| {3, EVENT_LOG_TAG_BEACON_LOG, EVENT_LOG_SET_WL, "BEACON_LOG"}, |
| {2, EVENT_LOG_TAG_WL_ASSOC_LOG, EVENT_LOG_SET_WL, "ASSOC_LOG"}, |
| {2, EVENT_LOG_TAG_WL_ROAM_LOG, EVENT_LOG_SET_WL, "ROAM_LOG"}, |
| {1, EVENT_LOG_TAG_TRACE_WL_INFO, EVENT_LOG_SET_WL, "WL_INFO"}, |
| {1, EVENT_LOG_TAG_TRACE_BTCOEX_INFO, EVENT_LOG_SET_WL, "BTCOEX_INFO"}, |
| #ifdef CUSTOMER_HW4_DEBUG |
| {3, EVENT_LOG_TAG_SCAN_WARN, EVENT_LOG_SET_WL, "SCAN_WARN"}, |
| #else |
| {1, EVENT_LOG_TAG_SCAN_WARN, EVENT_LOG_SET_WL, "SCAN_WARN"}, |
| #endif /* CUSTOMER_HW4_DEBUG */ |
| {1, EVENT_LOG_TAG_SCAN_ERROR, EVENT_LOG_SET_WL, "SCAN_ERROR"}, |
| {2, EVENT_LOG_TAG_SCAN_TRACE_LOW, EVENT_LOG_SET_WL, "SCAN_TRACE_LOW"}, |
| {2, EVENT_LOG_TAG_SCAN_TRACE_HIGH, EVENT_LOG_SET_WL, "SCAN_TRACE_HIGH"} |
| }; |
| |
| struct log_level_table fw_event_level_map[] = { |
| {1, EVENT_LOG_TAG_TRACE_WL_INFO, EVENT_LOG_SET_WL, "WL_INFO"}, |
| {1, EVENT_LOG_TAG_TRACE_BTCOEX_INFO, EVENT_LOG_SET_WL, "BTCOEX_INFO"}, |
| #ifdef CUSTOMER_HW4_DEBUG |
| {3, EVENT_LOG_TAG_BEACON_LOG, EVENT_LOG_SET_WL, "BEACON LOG"}, |
| #else |
| {2, EVENT_LOG_TAG_BEACON_LOG, EVENT_LOG_SET_WL, "BEACON LOG"}, |
| #endif /* CUSTOMER_HW4_DEBUG */ |
| }; |
| |
| struct map_table nan_event_map[] = { |
| {TRACE_NAN_CLUSTER_STARTED, NAN_EVENT_CLUSTER_STARTED, "NAN_CLUSTER_STARTED"}, |
| {TRACE_NAN_CLUSTER_JOINED, NAN_EVENT_CLUSTER_JOINED, "NAN_CLUSTER_JOINED"}, |
| {TRACE_NAN_CLUSTER_MERGED, NAN_EVENT_CLUSTER_MERGED, "NAN_CLUSTER_MERGED"}, |
| {TRACE_NAN_ROLE_CHANGED, NAN_EVENT_ROLE_CHANGED, "NAN_ROLE_CHANGED"}, |
| {TRACE_NAN_SCAN_COMPLETE, NAN_EVENT_SCAN_COMPLETE, "NAN_SCAN_COMPLETE"}, |
| {TRACE_NAN_STATUS_CHNG, NAN_EVENT_STATUS_CHNG, "NAN_STATUS_CHNG"}, |
| }; |
| |
| struct log_level_table nan_event_level_map[] = { |
| {1, EVENT_LOG_TAG_NAN_ERROR, 0, "NAN_ERROR"}, |
| {2, EVENT_LOG_TAG_NAN_INFO, 0, "NAN_INFO"}, |
| {3, EVENT_LOG_TAG_NAN_DBG, 0, "NAN_DEBUG"}, |
| }; |
| |
| struct map_table nan_evt_tag_map[] = { |
| {TRACE_TAG_BSSID, WIFI_TAG_BSSID, "BSSID"}, |
| {TRACE_TAG_ADDR, WIFI_TAG_ADDR, "ADDR_0"}, |
| }; |
| |
| /* reference tab table */ |
| uint ref_tag_tbl[EVENT_LOG_TAG_MAX + 1] = {0}; |
| |
| typedef struct dhddbg_loglist_item { |
| dll_t list; |
| event_log_hdr_t *hdr; |
| } loglist_item_t; |
| |
| typedef struct dhbdbg_pending_item { |
| dll_t list; |
| dhd_dbg_ring_status_t ring_status; |
| dhd_dbg_ring_entry_t *ring_entry; |
| } pending_item_t; |
| |
| /* trace log entry header user space processing */ |
| struct tracelog_header { |
| int magic_num; |
| int buf_size; |
| int seq_num; |
| }; |
| #define TRACE_LOG_MAGIC_NUMBER 0xEAE47C06 |
| |
| int |
| dhd_dbg_ring_pull_single(dhd_pub_t *dhdp, int ring_id, void *data, uint32 buf_len, |
| bool strip_header) |
| { |
| dhd_dbg_ring_t *ring; |
| dhd_dbg_ring_entry_t *r_entry; |
| uint32 rlen; |
| char *buf; |
| |
| if (!dhdp || !dhdp->dbg) { |
| return 0; |
| } |
| |
| ring = &dhdp->dbg->dbg_rings[ring_id]; |
| |
| if (ring->state != RING_ACTIVE) { |
| return 0; |
| } |
| |
| if (ring->rp == ring->wp) { |
| return 0; |
| } |
| |
| r_entry = (dhd_dbg_ring_entry_t *)((uint8 *)ring->ring_buf + ring->rp); |
| |
| /* Boundary Check */ |
| rlen = ENTRY_LENGTH(r_entry); |
| if ((ring->rp + rlen) > ring->ring_size) { |
| DHD_ERROR(("%s: entry len %d is out of boundary of ring size %d," |
| " current ring %d[%s] - rp=%d\n", __FUNCTION__, rlen, |
| ring->ring_size, ring->id, ring->name, ring->rp)); |
| return 0; |
| } |
| |
| if (strip_header) { |
| rlen = r_entry->len; |
| buf = (char *)r_entry + DBG_RING_ENTRY_SIZE; |
| } else { |
| rlen = ENTRY_LENGTH(r_entry); |
| buf = (char *)r_entry; |
| } |
| if (rlen > buf_len) { |
| DHD_ERROR(("%s: buf len %d is too small for entry len %d\n", |
| __FUNCTION__, buf_len, rlen)); |
| DHD_ERROR(("%s: ring %d[%s] - ring size=%d, wp=%d, rp=%d\n", |
| __FUNCTION__, ring->id, ring->name, ring->ring_size, |
| ring->wp, ring->rp)); |
| ASSERT(0); |
| return 0; |
| } |
| |
| memcpy(data, buf, rlen); |
| /* update ring context */ |
| ring->rp += ENTRY_LENGTH(r_entry); |
| /* skip padding if there is one */ |
| if (ring->tail_padded && ((ring->rp + ring->rem_len) == ring->ring_size)) { |
| DHD_DBGIF(("%s: RING%d[%s] Found padding, rp=%d, wp=%d\n", |
| __FUNCTION__, ring->id, ring->name, ring->rp, ring->wp)); |
| ring->rp = 0; |
| ring->tail_padded = FALSE; |
| ring->rem_len = 0; |
| } |
| if (ring->rp >= ring->ring_size) { |
| DHD_ERROR(("%s: RING%d[%s] rp pointed out of ring boundary," |
| " rp=%d, ring_size=%d\n", __FUNCTION__, ring->id, |
| ring->name, ring->rp, ring->ring_size)); |
| ASSERT(0); |
| } |
| ring->stat.read_bytes += ENTRY_LENGTH(r_entry); |
| DHD_DBGIF(("%s RING%d[%s]read_bytes %d, wp=%d, rp=%d\n", __FUNCTION__, |
| ring->id, ring->name, ring->stat.read_bytes, ring->wp, ring->rp)); |
| |
| return rlen; |
| } |
| |
| int |
| dhd_dbg_ring_pull(dhd_pub_t *dhdp, int ring_id, void *data, uint32 buf_len) |
| { |
| int32 r_len, total_r_len = 0; |
| dhd_dbg_ring_t *ring; |
| |
| if (!dhdp || !dhdp->dbg) |
| return 0; |
| ring = &dhdp->dbg->dbg_rings[ring_id]; |
| if (ring->state != RING_ACTIVE) |
| return 0; |
| |
| while (buf_len > 0) { |
| r_len = dhd_dbg_ring_pull_single(dhdp, ring_id, data, buf_len, FALSE); |
| if (r_len == 0) |
| break; |
| data = (uint8 *)data + r_len; |
| buf_len -= r_len; |
| total_r_len += r_len; |
| } |
| |
| return total_r_len; |
| } |
| |
| int |
| dhd_dbg_ring_push(dhd_pub_t *dhdp, int ring_id, dhd_dbg_ring_entry_t *hdr, void *data) |
| { |
| unsigned long flags; |
| uint32 pending_len; |
| uint32 w_len; |
| uint32 avail_size; |
| dhd_dbg_ring_t *ring; |
| dhd_dbg_ring_entry_t *w_entry, *r_entry; |
| |
| if (!dhdp || !dhdp->dbg) { |
| return BCME_BADADDR; |
| } |
| |
| ring = &dhdp->dbg->dbg_rings[ring_id]; |
| |
| if (ring->state != RING_ACTIVE) { |
| return BCME_OK; |
| } |
| |
| flags = dhd_os_spin_lock(ring->lock); |
| |
| w_len = ENTRY_LENGTH(hdr); |
| |
| if (w_len > ring->ring_size) { |
| dhd_os_spin_unlock(ring->lock, flags); |
| return BCME_ERROR; |
| } |
| |
| /* Claim the space */ |
| do { |
| avail_size = DBG_RING_CHECK_WRITE_SPACE(ring->rp, ring->wp, ring->ring_size); |
| if (avail_size <= w_len) { |
| /* Prepare the space */ |
| if (ring->rp <= ring->wp) { |
| ring->tail_padded = TRUE; |
| ring->rem_len = ring->ring_size - ring->wp; |
| DHD_DBGIF(("%s: RING%d[%s] Insuffient tail space," |
| " rp=%d, wp=%d, rem_len=%d, ring_size=%d," |
| " avail_size=%d, w_len=%d\n", __FUNCTION__, |
| ring->id, ring->name, ring->rp, ring->wp, |
| ring->rem_len, ring->ring_size, avail_size, |
| w_len)); |
| |
| /* 0 pad insufficient tail space */ |
| memset((uint8 *)ring->ring_buf + ring->wp, 0, ring->rem_len); |
| if (ring->rp == ring->wp) { |
| ring->rp = 0; |
| } |
| ring->wp = 0; |
| } else { |
| /* Not enough space for new entry, free some up */ |
| r_entry = (dhd_dbg_ring_entry_t *)((uint8 *)ring->ring_buf + |
| ring->rp); |
| ring->rp += ENTRY_LENGTH(r_entry); |
| /* skip padding if there is one */ |
| if (ring->tail_padded && |
| ((ring->rp + ring->rem_len) == ring->ring_size)) { |
| DHD_DBGIF(("%s: RING%d[%s] Found padding," |
| " avail_size=%d, w_len=%d\n", __FUNCTION__, |
| ring->id, ring->name, avail_size, w_len)); |
| ring->rp = 0; |
| ring->tail_padded = FALSE; |
| ring->rem_len = 0; |
| } |
| if (ring->rp >= ring->ring_size) { |
| DHD_ERROR(("%s: RING%d[%s] rp points out of boundary," |
| " ring->rp = %d, ring->ring_size=%d\n", |
| __FUNCTION__, ring->id, ring->name, ring->rp, |
| ring->ring_size)); |
| ASSERT(0); |
| } |
| ring->stat.read_bytes += ENTRY_LENGTH(r_entry); |
| DHD_DBGIF(("%s: RING%d[%s] read_bytes %d, wp=%d, rp=%d\n", |
| __FUNCTION__, ring->id, ring->name, ring->stat.read_bytes, |
| ring->wp, ring->rp)); |
| } |
| } else { |
| break; |
| } |
| } while (TRUE); |
| |
| w_entry = (dhd_dbg_ring_entry_t *)((uint8 *)ring->ring_buf + ring->wp); |
| /* header */ |
| memcpy(w_entry, hdr, DBG_RING_ENTRY_SIZE); |
| w_entry->len = hdr->len; |
| /* payload */ |
| memcpy((char *)w_entry + DBG_RING_ENTRY_SIZE, data, w_entry->len); |
| /* update write pointer */ |
| ring->wp += w_len; |
| if (ring->wp >= ring->ring_size) { |
| DHD_ERROR(("%s: RING%d[%s] wp pointed out of ring boundary, " |
| "wp=%d, ring_size=%d\n", __FUNCTION__, ring->id, |
| ring->name, ring->wp, ring->ring_size)); |
| ASSERT(0); |
| } |
| /* update statistics */ |
| ring->stat.written_records++; |
| ring->stat.written_bytes += w_len; |
| DHD_DBGIF(("%s : RING%d[%s] written_records %d, written_bytes %d, read_bytes=%d," |
| " ring->threshold=%d, wp=%d, rp=%d\n", __FUNCTION__, ring->id, ring->name, |
| ring->stat.written_records, ring->stat.written_bytes, ring->stat.read_bytes, |
| ring->threshold, ring->wp, ring->rp)); |
| |
| /* Calculate current pending size */ |
| if (ring->stat.written_bytes > ring->stat.read_bytes) { |
| pending_len = ring->stat.written_bytes - ring->stat.read_bytes; |
| } else if (ring->stat.written_bytes < ring->stat.read_bytes) { |
| pending_len = 0xFFFFFFFF - ring->stat.read_bytes + ring->stat.written_bytes; |
| } else { |
| pending_len = 0; |
| } |
| |
| /* if the current pending size is bigger than threshold */ |
| if (ring->threshold > 0 && |
| (pending_len >= ring->threshold) && ring->sched_pull) { |
| dhdp->dbg->pullreq(dhdp->dbg->private, ring->id); |
| ring->sched_pull = FALSE; |
| } |
| dhd_os_spin_unlock(ring->lock, flags); |
| return BCME_OK; |
| } |
| |
| static int |
| dhd_dbg_msgtrace_seqchk(uint32 *prev, uint32 cur) |
| { |
| /* normal case including wrap around */ |
| if ((cur == 0 && *prev == 0xFFFFFFFF) || ((cur - *prev) == 1)) { |
| goto done; |
| } else if (cur == *prev) { |
| DHD_EVENT(("%s duplicate trace\n", __FUNCTION__)); |
| return -1; |
| } else if (cur > *prev) { |
| DHD_EVENT(("%s lost %d packets\n", __FUNCTION__, cur - *prev)); |
| } else { |
| DHD_EVENT(("%s seq out of order, dhd %d, dongle %d\n", |
| __FUNCTION__, *prev, cur)); |
| } |
| done: |
| *prev = cur; |
| return 0; |
| } |
| |
| #ifndef MACOSX_DHD |
| static void |
| dhd_dbg_msgtrace_msg_parser(void *event_data) |
| { |
| msgtrace_hdr_t *hdr; |
| char *data, *s; |
| static uint32 seqnum_prev = 0; |
| |
| hdr = (msgtrace_hdr_t *)event_data; |
| data = (char *)event_data + MSGTRACE_HDRLEN; |
| |
| /* There are 2 bytes available at the end of data */ |
| data[ntoh16(hdr->len)] = '\0'; |
| |
| if (ntoh32(hdr->discarded_bytes) || ntoh32(hdr->discarded_printf)) { |
| DHD_DBGIF(("WLC_E_TRACE: [Discarded traces in dongle -->" |
| "discarded_bytes %d discarded_printf %d]\n", |
| ntoh32(hdr->discarded_bytes), |
| ntoh32(hdr->discarded_printf))); |
| } |
| |
| if (dhd_dbg_msgtrace_seqchk(&seqnum_prev, ntoh32(hdr->seqnum))) |
| return; |
| |
| /* Display the trace buffer. Advance from |
| * \n to \n to avoid display big |
| * printf (issue with Linux printk ) |
| */ |
| while (*data != '\0' && (s = strstr(data, "\n")) != NULL) { |
| *s = '\0'; |
| DHD_FWLOG(("[FWLOG] %s\n", data)); |
| data = s+1; |
| } |
| if (*data) |
| DHD_FWLOG(("[FWLOG] %s", data)); |
| } |
| #endif /* MACOSX_DHD */ |
| #ifdef SHOW_LOGTRACE |
| static const uint8 * |
| event_get_tlv(uint16 id, const char* tlvs, uint tlvs_len) |
| { |
| const uint8 *pos = (const uint8 *)tlvs; |
| const uint8 *end = pos + tlvs_len; |
| const tlv_log *tlv; |
| int rest; |
| |
| while (pos + 1 < end) { |
| if (pos + 4 + pos[1] > end) |
| break; |
| tlv = (const tlv_log *) pos; |
| if (tlv->tag == id) |
| return pos; |
| rest = tlv->len % 4; /* padding values */ |
| pos += 4 + tlv->len + rest; |
| } |
| return NULL; |
| } |
| |
| #define DATA_UNIT_FOR_LOG_CNT 4 |
| /* #pragma used as a WAR to fix build failure, |
| * ignore dropping of 'const' qualifier in tlv_data assignment |
| * this pragma disables the warning only for the following function |
| */ |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wcast-qual" |
| #endif |
| static int |
| dhd_dbg_nan_event_handler(dhd_pub_t *dhdp, event_log_hdr_t *hdr, uint32 *data) |
| { |
| int ret = BCME_OK; |
| wl_event_log_id_ver_t nan_hdr; |
| log_nan_event_t *evt_payload; |
| uint16 evt_payload_len = 0, tot_payload_len = 0; |
| dhd_dbg_ring_entry_t msg_hdr; |
| bool evt_match = FALSE; |
| event_log_hdr_t *ts_hdr; |
| uint32 *ts_data; |
| char *tlvs, *dest_tlvs; |
| tlv_log *tlv_data; |
| int tlv_len = 0; |
| int i = 0, evt_idx = 0; |
| char eaddr_buf[ETHER_ADDR_STR_LEN]; |
| |
| BCM_REFERENCE(eaddr_buf); |
| |
| nan_hdr.t = *data; |
| DHD_DBGIF(("%s: version %u event %x\n", __FUNCTION__, nan_hdr.version, |
| nan_hdr.event)); |
| |
| if (nan_hdr.version != DIAG_VERSION) { |
| DHD_ERROR(("Event payload version %u mismatch with current version %u\n", |
| nan_hdr.version, DIAG_VERSION)); |
| return BCME_VERSION; |
| } |
| |
| /* nan event log should at least contain a wl_event_log_id_ver_t |
| * header and a arm cycle count |
| */ |
| if (hdr->count < NAN_EVENT_LOG_MIN_LENGTH) { |
| return BCME_BADLEN; |
| } |
| |
| memset(&msg_hdr, 0, sizeof(dhd_dbg_ring_entry_t)); |
| ts_hdr = (event_log_hdr_t *)((uint8 *)data - sizeof(event_log_hdr_t)); |
| if (ts_hdr->tag == EVENT_LOG_TAG_TS) { |
| ts_data = (uint32 *)ts_hdr - ts_hdr->count; |
| msg_hdr.timestamp = (uint64)ts_data[0]; |
| msg_hdr.flags |= DBG_RING_ENTRY_FLAGS_HAS_TIMESTAMP; |
| } |
| msg_hdr.type = DBG_RING_ENTRY_NAN_EVENT_TYPE; |
| for (i = 0; i < ARRAYSIZE(nan_event_map); i++) { |
| if (nan_event_map[i].fw_id == nan_hdr.event) { |
| evt_match = TRUE; |
| evt_idx = i; |
| break; |
| } |
| } |
| if (evt_match) { |
| DHD_DBGIF(("%s : event (%s)\n", __FUNCTION__, nan_event_map[evt_idx].desc)); |
| /* payload length for nan event data */ |
| evt_payload_len = sizeof(log_nan_event_t) + |
| (hdr->count - 2) * DATA_UNIT_FOR_LOG_CNT; |
| if ((evt_payload = MALLOC(dhdp->osh, evt_payload_len)) == NULL) { |
| DHD_ERROR(("Memory allocation failed for nan evt log (%u)\n", |
| evt_payload_len)); |
| return BCME_NOMEM; |
| } |
| evt_payload->version = NAN_EVENT_VERSION; |
| evt_payload->event = nan_event_map[evt_idx].host_id; |
| dest_tlvs = (char *)evt_payload->tlvs; |
| tot_payload_len = sizeof(log_nan_event_t); |
| tlvs = (char *)(&data[1]); |
| tlv_len = (hdr->count - 2) * DATA_UNIT_FOR_LOG_CNT; |
| for (i = 0; i < ARRAYSIZE(nan_evt_tag_map); i++) { |
| tlv_data = (tlv_log *)event_get_tlv(nan_evt_tag_map[i].fw_id, |
| tlvs, tlv_len); |
| if (tlv_data) { |
| DHD_DBGIF(("NAN evt tlv.tag(%s), tlv.len : %d, tlv.data : ", |
| nan_evt_tag_map[i].desc, tlv_data->len)); |
| memcpy(dest_tlvs, tlv_data, sizeof(tlv_log) + tlv_data->len); |
| tot_payload_len += tlv_data->len + sizeof(tlv_log); |
| switch (tlv_data->tag) { |
| case TRACE_TAG_BSSID: |
| case TRACE_TAG_ADDR: |
| DHD_DBGIF(("%s\n", |
| bcm_ether_ntoa( |
| (const struct ether_addr *)tlv_data->value, |
| eaddr_buf))); |
| break; |
| default: |
| if (DHD_DBGIF_ON()) { |
| prhex(NULL, &tlv_data->value[0], |
| tlv_data->len); |
| } |
| break; |
| } |
| dest_tlvs += tlv_data->len + sizeof(tlv_log); |
| } |
| } |
| msg_hdr.flags |= DBG_RING_ENTRY_FLAGS_HAS_BINARY; |
| msg_hdr.len = tot_payload_len; |
| dhd_dbg_ring_push(dhdp, NAN_EVENT_RING_ID, &msg_hdr, evt_payload); |
| MFREE(dhdp->osh, evt_payload, evt_payload_len); |
| } |
| return ret; |
| } |
| |
| static int |
| dhd_dbg_custom_evnt_handler(dhd_pub_t *dhdp, event_log_hdr_t *hdr, uint32 *data) |
| { |
| int i = 0, match_idx = 0; |
| int payload_len, tlv_len; |
| uint16 tot_payload_len = 0; |
| int ret = BCME_OK; |
| int log_level; |
| wl_event_log_id_ver_t wl_log_id; |
| dhd_dbg_ring_entry_t msg_hdr; |
| log_conn_event_t *event_data; |
| bool evt_match = FALSE; |
| event_log_hdr_t *ts_hdr; |
| uint32 *ts_data; |
| char *tlvs, *dest_tlvs; |
| tlv_log *tlv_data; |
| static uint64 ts_saved = 0; |
| char eabuf[ETHER_ADDR_STR_LEN]; |
| char chanbuf[CHANSPEC_STR_LEN]; |
| |
| BCM_REFERENCE(eabuf); |
| BCM_REFERENCE(chanbuf); |
| /* get a event type and version */ |
| wl_log_id.t = *data; |
| if (wl_log_id.version != DIAG_VERSION) |
| return BCME_VERSION; |
| |
| /* custom event log should at least contain a wl_event_log_id_ver_t |
| * header and a arm cycle count |
| */ |
| if (hdr->count < NAN_EVENT_LOG_MIN_LENGTH) { |
| return BCME_BADLEN; |
| } |
| |
| ts_hdr = (event_log_hdr_t *)((uint8 *)data - sizeof(event_log_hdr_t)); |
| if (ts_hdr->tag == EVENT_LOG_TAG_TS) { |
| ts_data = (uint32 *)ts_hdr - ts_hdr->count; |
| ts_saved = (uint64)ts_data[0]; |
| } |
| memset(&msg_hdr, 0, sizeof(dhd_dbg_ring_entry_t)); |
| msg_hdr.timestamp = ts_saved; |
| |
| DHD_DBGIF(("Android Event ver %d, payload %d words, ts %llu\n", |
| (*data >> 16), hdr->count - 1, ts_saved)); |
| |
| /* Perform endian convertion */ |
| for (i = 0; i < hdr->count; i++) { |
| /* *(data + i) = ntoh32(*(data + i)); */ |
| DHD_DATA(("%08x ", *(data + i))); |
| } |
| DHD_DATA(("\n")); |
| msg_hdr.flags |= DBG_RING_ENTRY_FLAGS_HAS_TIMESTAMP; |
| msg_hdr.flags |= DBG_RING_ENTRY_FLAGS_HAS_BINARY; |
| msg_hdr.type = DBG_RING_ENTRY_EVENT_TYPE; |
| |
| /* convert the data to log_conn_event_t format */ |
| for (i = 0; i < ARRAYSIZE(event_map); i++) { |
| if (event_map[i].fw_id == wl_log_id.event) { |
| evt_match = TRUE; |
| match_idx = i; |
| break; |
| } |
| } |
| if (evt_match) { |
| log_level = dhdp->dbg->dbg_rings[FW_EVENT_RING_ID].log_level; |
| /* filter the data based on log_level */ |
| for (i = 0; i < ARRAYSIZE(fw_event_level_map); i++) { |
| if ((fw_event_level_map[i].tag == hdr->tag) && |
| (fw_event_level_map[i].log_level > log_level)) { |
| return BCME_OK; |
| } |
| } |
| DHD_DBGIF(("%s : event (%s)\n", __FUNCTION__, event_map[match_idx].desc)); |
| /* get the payload length for event data (skip : log header + timestamp) */ |
| payload_len = sizeof(log_conn_event_t) + DATA_UNIT_FOR_LOG_CNT * (hdr->count - 2); |
| event_data = MALLOC(dhdp->osh, payload_len); |
| if (!event_data) { |
| DHD_ERROR(("failed to allocate the log_conn_event_t with length(%d)\n", |
| payload_len)); |
| return BCME_NOMEM; |
| } |
| event_data->event = event_map[match_idx].host_id; |
| dest_tlvs = (char *)event_data->tlvs; |
| tot_payload_len = sizeof(log_conn_event_t); |
| tlvs = (char *)(&data[1]); |
| tlv_len = (hdr->count - 2) * DATA_UNIT_FOR_LOG_CNT; |
| for (i = 0; i < ARRAYSIZE(event_tag_map); i++) { |
| tlv_data = (tlv_log *)event_get_tlv(event_tag_map[i].fw_id, |
| tlvs, tlv_len); |
| if (tlv_data) { |
| DHD_DBGIF(("tlv.tag(%s), tlv.len : %d, tlv.data : ", |
| event_tag_map[i].desc, tlv_data->len)); |
| memcpy(dest_tlvs, tlv_data, sizeof(tlv_log) + tlv_data->len); |
| tot_payload_len += tlv_data->len + sizeof(tlv_log); |
| switch (tlv_data->tag) { |
| case TRACE_TAG_BSSID: |
| case TRACE_TAG_ADDR: |
| case TRACE_TAG_ADDR1: |
| case TRACE_TAG_ADDR2: |
| case TRACE_TAG_ADDR3: |
| case TRACE_TAG_ADDR4: |
| DHD_DBGIF(("%s\n", |
| bcm_ether_ntoa((const struct ether_addr *)tlv_data->value, |
| eabuf))); |
| break; |
| case TRACE_TAG_SSID: |
| DHD_DBGIF(("%s\n", tlv_data->value)); |
| break; |
| case TRACE_TAG_STATUS: |
| DHD_DBGIF(("%d\n", ltoh32_ua(&tlv_data->value[0]))); |
| break; |
| case TRACE_TAG_REASON_CODE: |
| DHD_DBGIF(("%d\n", ltoh16_ua(&tlv_data->value[0]))); |
| break; |
| case TRACE_TAG_RATE_MBPS: |
| DHD_DBGIF(("%d Kbps\n", |
| ltoh16_ua(&tlv_data->value[0]) * 500)); |
| break; |
| case TRACE_TAG_CHANNEL_SPEC: |
| DHD_DBGIF(("%s\n", |
| wf_chspec_ntoa( |
| ltoh16_ua(&tlv_data->value[0]), chanbuf))); |
| break; |
| default: |
| if (DHD_DBGIF_ON()) { |
| prhex(NULL, &tlv_data->value[0], tlv_data->len); |
| } |
| } |
| dest_tlvs += tlv_data->len + sizeof(tlv_log); |
| } |
| } |
| msg_hdr.len = tot_payload_len; |
| dhd_dbg_ring_push(dhdp, FW_EVENT_RING_ID, &msg_hdr, event_data); |
| MFREE(dhdp->osh, event_data, payload_len); |
| } |
| return ret; |
| } |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic pop |
| #endif |
| |
| /* To identify format of types %Ns where N >= 0 is a number */ |
| bool |
| check_valid_string_format(char *curr_ptr) |
| { |
| char *next_ptr; |
| if ((next_ptr = bcmstrstr(curr_ptr, "s")) != NULL) { |
| /* Default %s format */ |
| if (curr_ptr == next_ptr) { |
| return TRUE; |
| } |
| |
| /* Verify each charater between '%' and 's' is a valid number */ |
| while (curr_ptr < next_ptr) { |
| if (bcm_isdigit(*curr_ptr) == FALSE) { |
| return FALSE; |
| } |
| curr_ptr++; |
| } |
| |
| return TRUE; |
| } else { |
| return FALSE; |
| } |
| } |
| |
| #define MAX_NO_OF_ARG 16 |
| #define FMTSTR_SIZE 132 |
| #define ROMSTR_SIZE 200 |
| #define SIZE_LOC_STR 50 |
| static uint64 verboselog_ts_saved = 0; |
| static void |
| dhd_dbg_verboselog_handler(dhd_pub_t *dhdp, event_log_hdr_t *hdr, |
| void *raw_event_ptr) |
| { |
| event_log_hdr_t *ts_hdr; |
| uint32 *log_ptr = (uint32 *)hdr - hdr->count; |
| char fmtstr_loc_buf[ROMSTR_SIZE] = { 0 }; |
| uint32 rom_str_len = 0; |
| uint32 *ts_data; |
| |
| if (!raw_event_ptr) { |
| return; |
| } |
| |
| /* Get time stamp if it's updated */ |
| ts_hdr = (event_log_hdr_t *)((char *)log_ptr - sizeof(event_log_hdr_t)); |
| if (ts_hdr->tag == EVENT_LOG_TAG_TS) { |
| ts_data = (uint32 *)ts_hdr - ts_hdr->count; |
| verboselog_ts_saved = (uint64)ts_data[0]; |
| DHD_MSGTRACE_LOG(("EVENT_LOG_TS[0x%08x]: SYS:%08x CPU:%08x\n", |
| ts_data[ts_hdr->count - 1], ts_data[0], ts_data[1])); |
| } |
| |
| if (hdr->tag == EVENT_LOG_TAG_ROM_PRINTF) { |
| rom_str_len = (hdr->count - 1) * sizeof(uint32); |
| if (rom_str_len >= (ROMSTR_SIZE -1)) |
| rom_str_len = ROMSTR_SIZE - 1; |
| |
| /* copy all ascii data for ROM printf to local string */ |
| memcpy(fmtstr_loc_buf, log_ptr, rom_str_len); |
| /* add end of line at last */ |
| fmtstr_loc_buf[rom_str_len] = '\0'; |
| |
| DHD_MSGTRACE_LOG(("EVENT_LOG_ROM[0x%08x]: %s", |
| log_ptr[hdr->count - 1], fmtstr_loc_buf)); |
| |
| /* Add newline if missing */ |
| if (fmtstr_loc_buf[strlen(fmtstr_loc_buf) - 1] != '\n') |
| DHD_MSGTRACE_LOG(("\n")); |
| |
| return; |
| } |
| |
| if (hdr->tag == EVENT_LOG_TAG_MSCHPROFILE || hdr->tag == EVENT_LOG_TAG_MSCHPROFILE_TLV) { |
| wl_mschdbg_verboselog_handler(dhdp, raw_event_ptr, hdr->tag, log_ptr); |
| return; |
| } |
| |
| /* print the message out in a logprint */ |
| dhd_dbg_verboselog_printf(dhdp, hdr, raw_event_ptr, log_ptr); |
| } |
| |
| void |
| dhd_dbg_verboselog_printf(dhd_pub_t *dhdp, event_log_hdr_t *hdr, |
| void *raw_event_ptr, uint32 *log_ptr) |
| { |
| dhd_event_log_t *raw_event = (dhd_event_log_t *)raw_event_ptr; |
| uint16 count; |
| int log_level, id; |
| char fmtstr_loc_buf[ROMSTR_SIZE] = { 0 }; |
| char (*str_buf)[SIZE_LOC_STR] = NULL; |
| char *str_tmpptr = NULL; |
| uint32 addr = 0; |
| typedef union { |
| uint32 val; |
| char * addr; |
| } u_arg; |
| u_arg arg[MAX_NO_OF_ARG] = {{0}}; |
| char *c_ptr = NULL; |
| |
| BCM_REFERENCE(arg); |
| |
| if (!raw_event) { |
| return; |
| } |
| |
| /* print the message out in a logprint */ |
| if (!(raw_event->fmts) || hdr->fmt_num == 0xffff) { |
| if (dhdp->dbg) { |
| log_level = dhdp->dbg->dbg_rings[FW_VERBOSE_RING_ID].log_level; |
| for (id = 0; id < ARRAYSIZE(fw_verbose_level_map); id++) { |
| if ((fw_verbose_level_map[id].tag == hdr->tag) && |
| (fw_verbose_level_map[id].log_level > log_level)) |
| return; |
| } |
| } |
| |
| DHD_EVENT(("%d.%d EL:tag=%d len=%d fmt=0x%x", |
| (uint32)verboselog_ts_saved / 1000, |
| (uint32)verboselog_ts_saved % 1000, |
| hdr->tag, |
| hdr->count, |
| hdr->fmt_num)); |
| |
| for (count = 0; count < (hdr->count-1); count++) { |
| if (count % 8 == 0) |
| DHD_EVENT(("\n\t%08x", log_ptr[count])); |
| else |
| DHD_EVENT((" %08x", log_ptr[count])); |
| } |
| DHD_EVENT(("\n")); |
| |
| return; |
| } |
| |
| str_buf = MALLOCZ(dhdp->osh, (MAX_NO_OF_ARG * SIZE_LOC_STR)); |
| if (!str_buf) { |
| DHD_ERROR(("%s: malloc failed str_buf\n", __FUNCTION__)); |
| return; |
| } |
| |
| if ((hdr->fmt_num >> 2) < raw_event->num_fmts) { |
| if (hdr->tag == EVENT_LOG_TAG_MSCHPROFILE) { |
| snprintf(fmtstr_loc_buf, FMTSTR_SIZE, "%s", |
| raw_event->fmts[hdr->fmt_num >> 2]); |
| hdr->count++; |
| } else { |
| snprintf(fmtstr_loc_buf, FMTSTR_SIZE, "CONSOLE_E: %6d.%3d %s", |
| log_ptr[hdr->count-1]/1000, (log_ptr[hdr->count - 1] % 1000), |
| raw_event->fmts[hdr->fmt_num >> 2]); |
| } |
| c_ptr = fmtstr_loc_buf; |
| } else { |
| DHD_ERROR(("%s: fmt number out of range \n", __FUNCTION__)); |
| goto exit; |
| } |
| |
| for (count = 0; count < (hdr->count - 1); count++) { |
| if (c_ptr != NULL) |
| if ((c_ptr = bcmstrstr(c_ptr, "%")) != NULL) |
| c_ptr++; |
| |
| if (c_ptr != NULL) { |
| if (check_valid_string_format(c_ptr)) { |
| if ((raw_event->raw_sstr) && |
| ((log_ptr[count] > raw_event->rodata_start) && |
| (log_ptr[count] < raw_event->rodata_end))) { |
| /* ram static string */ |
| addr = log_ptr[count] - raw_event->rodata_start; |
| str_tmpptr = raw_event->raw_sstr + addr; |
| memcpy(str_buf[count], str_tmpptr, |
| SIZE_LOC_STR); |
| str_buf[count][SIZE_LOC_STR-1] = '\0'; |
| arg[count].addr = str_buf[count]; |
| } else if ((raw_event->rom_raw_sstr) && |
| ((log_ptr[count] > |
| raw_event->rom_rodata_start) && |
| (log_ptr[count] < |
| raw_event->rom_rodata_end))) { |
| /* rom static string */ |
| addr = log_ptr[count] - raw_event->rom_rodata_start; |
| str_tmpptr = raw_event->rom_raw_sstr + addr; |
| memcpy(str_buf[count], str_tmpptr, |
| SIZE_LOC_STR); |
| str_buf[count][SIZE_LOC_STR-1] = '\0'; |
| arg[count].addr = str_buf[count]; |
| } else { |
| /* |
| * Dynamic string OR |
| * No data for static string. |
| * So store all string's address as string. |
| */ |
| snprintf(str_buf[count], SIZE_LOC_STR, |
| "(s)0x%x", log_ptr[count]); |
| arg[count].addr = str_buf[count]; |
| } |
| } else { |
| /* Other than string */ |
| arg[count].val = log_ptr[count]; |
| } |
| } |
| } |
| |
| /* Print FW logs */ |
| DHD_FWLOG((fmtstr_loc_buf, arg[0], arg[1], arg[2], arg[3], |
| arg[4], arg[5], arg[6], arg[7], arg[8], arg[9], arg[10], |
| arg[11], arg[12], arg[13], arg[14], arg[15])); |
| |
| exit: |
| MFREE(dhdp->osh, str_buf, (MAX_NO_OF_ARG * SIZE_LOC_STR)); |
| } |
| |
| static void |
| dhd_dbg_msgtrace_log_parser(dhd_pub_t *dhdp, void *event_data, |
| void *raw_event_ptr, uint datalen) |
| { |
| msgtrace_hdr_t *hdr; |
| char *data; |
| int id; |
| uint32 log_hdr_len = sizeof(event_log_hdr_t); |
| uint32 log_pyld_len; |
| static uint32 seqnum_prev = 0; |
| event_log_hdr_t *log_hdr; |
| bool msg_processed = FALSE; |
| uint32 *log_ptr = NULL; |
| dll_t list_head, *cur; |
| loglist_item_t *log_item; |
| int32 nan_evt_ring_log_level = 0; |
| dhd_dbg_ring_entry_t msg_hdr; |
| char *logbuf; |
| struct tracelog_header *logentry_header; |
| |
| /* log trace event consists of: |
| * msgtrace header |
| * event log block header |
| * event log payload |
| */ |
| if (datalen <= MSGTRACE_HDRLEN + EVENT_LOG_BLOCK_HDRLEN) { |
| return; |
| } |
| hdr = (msgtrace_hdr_t *)event_data; |
| data = (char *)event_data + MSGTRACE_HDRLEN; |
| datalen -= MSGTRACE_HDRLEN; |
| |
| if (dhd_dbg_msgtrace_seqchk(&seqnum_prev, ntoh32(hdr->seqnum))) |
| return; |
| |
| /* Save the whole message to event log ring */ |
| memset(&msg_hdr, 0, sizeof(dhd_dbg_ring_entry_t)); |
| logbuf = VMALLOC(dhdp->osh, sizeof(*logentry_header) + datalen); |
| if (logbuf == NULL) |
| return; |
| logentry_header = (struct tracelog_header *)logbuf; |
| logentry_header->magic_num = TRACE_LOG_MAGIC_NUMBER; |
| logentry_header->buf_size = datalen; |
| logentry_header->seq_num = hdr->seqnum; |
| msg_hdr.type = DBG_RING_ENTRY_DATA_TYPE; |
| |
| if ((sizeof(*logentry_header) + datalen) > PAYLOAD_MAX_LEN) { |
| DHD_ERROR(("%s:Payload len=%u exceeds max len\n", __FUNCTION__, |
| ((uint)sizeof(*logentry_header) + datalen))); |
| VMFREE(dhdp->osh, logbuf, sizeof(*logentry_header) + datalen); |
| return; |
| } |
| |
| msg_hdr.len = sizeof(*logentry_header) + datalen; |
| memcpy(logbuf + sizeof(*logentry_header), data, datalen); |
| dhd_dbg_ring_push(dhdp, FW_VERBOSE_RING_ID, &msg_hdr, logbuf); |
| VMFREE(dhdp->osh, logbuf, sizeof(*logentry_header) + datalen); |
| |
| /* Print sequence number, originating set and length of received |
| * event log buffer. Refer to event log buffer structure in |
| * event_log.h |
| */ |
| DHD_MSGTRACE_LOG(("EVENT_LOG_HDR[0x%x]: Set: 0x%08x length = %d\n", |
| ltoh16(*((uint16 *)(data+2))), ltoh32(*((uint32 *)(data + 4))), |
| ltoh16(*((uint16 *)(data))))); |
| data += EVENT_LOG_BLOCK_HDRLEN; |
| datalen -= EVENT_LOG_BLOCK_HDRLEN; |
| |
| /* start parsing from the tail of packet |
| * Sameple format of a meessage |
| * 001d3c54 00000064 00000064 001d3c54 001dba08 035d6ce1 0c540639 |
| * 001d3c54 00000064 00000064 035d6d89 0c580439 |
| * 0x0c580439 -- 39 is tag, 04 is count, 580c is format number |
| * all these uint32 values comes in reverse order as group as EL data |
| * while decoding we can only parse from last to first |
| * |<- datalen ->| |
| * |----(payload and maybe more logs)----|event_log_hdr_t| |
| * data log_hdr |
| */ |
| dll_init(&list_head); |
| while (datalen > log_hdr_len) { |
| log_hdr = (event_log_hdr_t *)(data + datalen - log_hdr_len); |
| /* skip zero padding at end of frame */ |
| if (log_hdr->tag == EVENT_LOG_TAG_NULL) { |
| datalen -= log_hdr_len; |
| continue; |
| } |
| /* Check argument count, any event log should contain at least |
| * one argument (4 bytes) for arm cycle count and up to 16 |
| * arguments when the format is valid |
| */ |
| if (log_hdr->count == 0) { |
| break; |
| } |
| if ((log_hdr->count > MAX_NO_OF_ARG) && (log_hdr->fmt_num != 0xffff)) { |
| break; |
| } |
| |
| log_pyld_len = log_hdr->count * DATA_UNIT_FOR_LOG_CNT; |
| /* log data should not cross the event data boundary */ |
| if ((char *)log_hdr - data < log_pyld_len) |
| break; |
| /* skip 4 bytes time stamp packet */ |
| if (log_hdr->tag == EVENT_LOG_TAG_TS) { |
| datalen -= log_pyld_len + log_hdr_len; |
| continue; |
| } |
| if (!(log_item = MALLOC(dhdp->osh, sizeof(*log_item)))) { |
| DHD_ERROR(("%s allocating log list item failed\n", |
| __FUNCTION__)); |
| break; |
| } |
| log_item->hdr = log_hdr; |
| dll_insert(&log_item->list, &list_head); |
| datalen -= (log_pyld_len + log_hdr_len); |
| } |
| |
| while (!dll_empty(&list_head)) { |
| msg_processed = FALSE; |
| cur = dll_head_p(&list_head); |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wcast-qual" |
| #endif |
| log_item = (loglist_item_t *)container_of(cur, loglist_item_t, list); |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) |
| #pragma GCC diagnostic pop |
| #endif |
| log_hdr = log_item->hdr; |
| log_ptr = (uint32 *)log_hdr - log_hdr->count; |
| dll_delete(cur); |
| MFREE(dhdp->osh, log_item, sizeof(*log_item)); |
| |
| /* Before DHD debugability is implemented WLC_E_TRACE had been |
| * used to carry verbose logging from firmware. We need to |
| * be able to handle those messages even without a initialized |
| * debug layer. |
| */ |
| if (dhdp->dbg) { |
| /* check the data for NAN event ring; keeping first as small table */ |
| /* process only user configured to log */ |
| nan_evt_ring_log_level = dhdp->dbg->dbg_rings[NAN_EVENT_RING_ID].log_level; |
| if (dhdp->dbg->dbg_rings[NAN_EVENT_RING_ID].log_level) { |
| for (id = 0; id < ARRAYSIZE(nan_event_level_map); id++) { |
| if (nan_event_level_map[id].tag == log_hdr->tag) { |
| /* dont process if tag log level is greater |
| * than ring log level |
| */ |
| if (nan_event_level_map[id].log_level > |
| nan_evt_ring_log_level) { |
| msg_processed = TRUE; |
| break; |
| } |
| /* In case of BCME_VERSION error, |
| * this is not NAN event type data |
| */ |
| if (dhd_dbg_nan_event_handler(dhdp, |
| log_hdr, log_ptr) != BCME_VERSION) { |
| msg_processed = TRUE; |
| } |
| break; |
| } |
| } |
| } |
| if (!msg_processed) { |
| /* check the data for event ring */ |
| for (id = 0; id < ARRAYSIZE(fw_event_level_map); id++) { |
| if (fw_event_level_map[id].tag == log_hdr->tag) { |
| /* In case of BCME_VERSION error, |
| * this is not event type data |
| */ |
| if (dhd_dbg_custom_evnt_handler(dhdp, |
| log_hdr, log_ptr) != BCME_VERSION) { |
| msg_processed = TRUE; |
| } |
| break; |
| } |
| } |
| } |
| } |
| if (!msg_processed) |
| dhd_dbg_verboselog_handler(dhdp, log_hdr, raw_event_ptr); |
| |
| } |
| } |
| #else /* !SHOW_LOGTRACE */ |
| static INLINE void dhd_dbg_verboselog_handler(dhd_pub_t *dhdp, |
| event_log_hdr_t *hdr, void *raw_event_ptr) {}; |
| static INLINE void dhd_dbg_msgtrace_log_parser(dhd_pub_t *dhdp, |
| void *event_data, void *raw_event_ptr, uint datalen) {}; |
| #endif /* SHOW_LOGTRACE */ |
| #ifndef MACOSX_DHD |
| void |
| dhd_dbg_trace_evnt_handler(dhd_pub_t *dhdp, void *event_data, |
| void *raw_event_ptr, uint datalen) |
| { |
| msgtrace_hdr_t *hdr; |
| |
| hdr = (msgtrace_hdr_t *)event_data; |
| |
| if (hdr->version != MSGTRACE_VERSION) { |
| DHD_DBGIF(("%s unsupported MSGTRACE version, dhd %d, dongle %d\n", |
| __FUNCTION__, MSGTRACE_VERSION, hdr->version)); |
| return; |
| } |
| |
| if (hdr->trace_type == MSGTRACE_HDR_TYPE_MSG) |
| dhd_dbg_msgtrace_msg_parser(event_data); |
| else if (hdr->trace_type == MSGTRACE_HDR_TYPE_LOG) |
| dhd_dbg_msgtrace_log_parser(dhdp, event_data, raw_event_ptr, datalen); |
| } |
| #endif /* MACOSX_DHD */ |
| static int |
| dhd_dbg_ring_init(dhd_pub_t *dhdp, dhd_dbg_ring_t *ring, uint16 id, uint8 *name, |
| uint32 ring_sz, int section) |
| { |
| void *buf; |
| unsigned long flags; |
| #ifdef CONFIG_DHD_USE_STATIC_BUF |
| buf = DHD_OS_PREALLOC(dhdp, section, ring_sz); |
| #else |
| buf = MALLOCZ(dhdp->osh, ring_sz); |
| #endif |
| if (!buf) |
| return BCME_NOMEM; |
| |
| ring->lock = dhd_os_spin_lock_init(dhdp->osh); |
| |
| flags = dhd_os_spin_lock(ring->lock); |
| ring->id = id; |
| strncpy(ring->name, name, DBGRING_NAME_MAX); |
| ring->name[DBGRING_NAME_MAX - 1] = 0; |
| ring->ring_size = ring_sz; |
| ring->wp = ring->rp = 0; |
| ring->ring_buf = buf; |
| ring->threshold = DBGRING_FLUSH_THRESHOLD(ring); |
| ring->state = RING_SUSPEND; |
| ring->sched_pull = TRUE; |
| ring->rem_len = 0; |
| dhd_os_spin_unlock(ring->lock, flags); |
| |
| return BCME_OK; |
| } |
| |
| static void |
| dhd_dbg_ring_deinit(dhd_pub_t *dhdp, dhd_dbg_ring_t *ring) |
| { |
| void *buf; |
| uint32 ring_sz; |
| unsigned long flags; |
| |
| if (!ring->ring_buf) |
| return; |
| |
| flags = dhd_os_spin_lock(ring->lock); |
| ring->id = 0; |
| ring->name[0] = 0; |
| ring_sz = ring->ring_size; |
| ring->ring_size = 0; |
| ring->wp = ring->rp = 0; |
| buf = ring->ring_buf; |
| ring->ring_buf = NULL; |
| memset(&ring->stat, 0, sizeof(ring->stat)); |
| ring->threshold = 0; |
| ring->state = RING_STOP; |
| dhd_os_spin_unlock(ring->lock, flags); |
| |
| dhd_os_spin_lock_deinit(dhdp->osh, ring->lock); |
| #ifndef CONFIG_DHD_USE_STATIC_BUF |
| MFREE(dhdp->osh, buf, ring_sz); |
| #endif |
| } |
| |
| uint8 |
| dhd_dbg_find_sets_by_tag(uint16 tag) |
| { |
| uint i; |
| uint8 sets = 0; |
| |
| for (i = 0; i < ARRAYSIZE(fw_verbose_level_map); i++) { |
| if (fw_verbose_level_map[i].tag == tag) { |
| sets |= fw_verbose_level_map[i].sets; |
| } |
| } |
| |
| for (i = 0; i < ARRAYSIZE(fw_event_level_map); i++) { |
| if (fw_event_level_map[i].tag == tag) { |
| sets |= fw_event_level_map[i].sets; |
| } |
| } |
| |
| return sets; |
| } |
| |
| /* |
| * dhd_dbg_set_event_log_tag : modify the state of an event log tag |
| */ |
| void |
| dhd_dbg_set_event_log_tag(dhd_pub_t *dhdp, uint16 tag, uint8 set) |
| { |
| wl_el_tag_params_t pars; |
| char *cmd = "event_log_tag_control"; |
| char iovbuf[WLC_IOCTL_SMLEN] = { 0 }; |
| int ret; |
| |
| memset(&pars, 0, sizeof(pars)); |
| pars.tag = tag; |
| pars.set = dhd_dbg_find_sets_by_tag(tag); |
| pars.flags = set ? EVENT_LOG_TAG_FLAG_LOG : EVENT_LOG_TAG_FLAG_NONE; |
| |
| if (!bcm_mkiovar(cmd, (char *)&pars, sizeof(pars), iovbuf, sizeof(iovbuf))) { |
| DHD_ERROR(("%s mkiovar failed\n", __FUNCTION__)); |
| return; |
| } |
| |
| ret = dhd_wl_ioctl_cmd(dhdp, WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0); |
| // if (ret) { |
| // DHD_ERROR(("%s set log tag iovar failed %d\n", __FUNCTION__, ret)); |
| // } |
| } |
| |
| int |
| dhd_dbg_set_configuration(dhd_pub_t *dhdp, int ring_id, int log_level, int flags, uint32 threshold) |
| { |
| dhd_dbg_ring_t *ring; |
| uint8 set = 1; |
| unsigned long lock_flags; |
| int i, array_len = 0; |
| struct log_level_table *log_level_tbl = NULL; |
| if (!dhdp || !dhdp->dbg) |
| return BCME_BADADDR; |
| |
| ring = &dhdp->dbg->dbg_rings[ring_id]; |
| |
| if (ring->state == RING_STOP) |
| return BCME_UNSUPPORTED; |
| |
| lock_flags = dhd_os_spin_lock(ring->lock); |
| if (log_level == 0) |
| ring->state = RING_SUSPEND; |
| else |
| ring->state = RING_ACTIVE; |
| ring->log_level = log_level; |
| |
| ring->threshold = MIN(threshold, DBGRING_FLUSH_THRESHOLD(ring)); |
| dhd_os_spin_unlock(ring->lock, lock_flags); |
| if (log_level > 0) |
| set = TRUE; |
| |
| if (ring->id == FW_EVENT_RING_ID) { |
| log_level_tbl = fw_event_level_map; |
| array_len = ARRAYSIZE(fw_event_level_map); |
| } else if (ring->id == FW_VERBOSE_RING_ID) { |
| log_level_tbl = fw_verbose_level_map; |
| array_len = ARRAYSIZE(fw_verbose_level_map); |
| } else if (ring->id == NAN_EVENT_RING_ID) { |
| log_level_tbl = nan_event_level_map; |
| array_len = ARRAYSIZE(nan_event_level_map); |
| } |
| |
| for (i = 0; i < array_len; i++) { |
| if (log_level == 0 || (log_level_tbl[i].log_level > log_level)) { |
| /* clear the reference per ring */ |
| ref_tag_tbl[log_level_tbl[i].tag] &= ~(1 << ring_id); |
| } else { |
| /* set the reference per ring */ |
| ref_tag_tbl[log_level_tbl[i].tag] |= (1 << ring_id); |
| } |
| set = (ref_tag_tbl[log_level_tbl[i].tag])? 1 : 0; |
| DHD_DBGIF(("%s TAG(%s) is %s for the ring(%s)\n", __FUNCTION__, |
| log_level_tbl[i].desc, (set)? "SET" : "CLEAR", ring->name)); |
| dhd_dbg_set_event_log_tag(dhdp, log_level_tbl[i].tag, set); |
| } |
| return BCME_OK; |
| } |
| |
| /* |
| * dhd_dbg_get_ring_status : get the ring status from the coresponding ring buffer |
| * Return: An error code or 0 on success. |
| */ |
| |
| int |
| dhd_dbg_get_ring_status(dhd_pub_t *dhdp, int ring_id, dhd_dbg_ring_status_t *dbg_ring_status) |
| { |
| int ret = BCME_OK; |
| int id = 0; |
| dhd_dbg_t *dbg; |
| dhd_dbg_ring_t *dbg_ring; |
| dhd_dbg_ring_status_t ring_status; |
| if (!dhdp || !dhdp->dbg) |
| return BCME_BADADDR; |
| dbg = dhdp->dbg; |
| |
| memset(&ring_status, 0, sizeof(dhd_dbg_ring_status_t)); |
| for (id = DEBUG_RING_ID_INVALID + 1; id < DEBUG_RING_ID_MAX; id++) { |
| dbg_ring = &dbg->dbg_rings[id]; |
| if (VALID_RING(dbg_ring->id) && (dbg_ring->id == ring_id)) { |
| RING_STAT_TO_STATUS(dbg_ring, ring_status); |
| *dbg_ring_status = ring_status; |
| break; |
| } |
| } |
| if (!VALID_RING(id)) { |
| DHD_ERROR(("%s : cannot find the ring_id : %d\n", __FUNCTION__, ring_id)); |
| ret = BCME_NOTFOUND; |
| } |
| return ret; |
| } |
| |
| /* |
| * dhd_dbg_find_ring_id : return ring_id based on ring_name |
| * Return: An invalid ring id for failure or valid ring id on success. |
| */ |
| |
| int |
| dhd_dbg_find_ring_id(dhd_pub_t *dhdp, char *ring_name) |
| { |
| int id; |
| dhd_dbg_t *dbg; |
| dhd_dbg_ring_t *ring; |
| |
| if (!dhdp || !dhdp->dbg) |
| return BCME_BADADDR; |
| |
| dbg = dhdp->dbg; |
| for (id = DEBUG_RING_ID_INVALID + 1; id < DEBUG_RING_ID_MAX; id++) { |
| ring = &dbg->dbg_rings[id]; |
| if (!strncmp((char *)ring->name, ring_name, sizeof(ring->name) - 1)) |
| break; |
| } |
| return id; |
| } |
| |
| /* |
| * dhd_dbg_get_priv : get the private data of dhd dbugability module |
| * Return : An NULL on failure or valid data address |
| */ |
| void * |
| dhd_dbg_get_priv(dhd_pub_t *dhdp) |
| { |
| if (!dhdp || !dhdp->dbg) |
| return NULL; |
| return dhdp->dbg->private; |
| } |
| |
| /* |
| * dhd_dbg_start : start and stop All of Ring buffers |
| * Return: An error code or 0 on success. |
| */ |
| int |
| dhd_dbg_start(dhd_pub_t *dhdp, bool start) |
| { |
| int ret = BCME_OK; |
| int ring_id; |
| dhd_dbg_t *dbg; |
| dhd_dbg_ring_t *dbg_ring; |
| if (!dhdp) |
| return BCME_BADARG; |
| dbg = dhdp->dbg; |
| |
| for (ring_id = DEBUG_RING_ID_INVALID + 1; ring_id < DEBUG_RING_ID_MAX; ring_id++) { |
| dbg_ring = &dbg->dbg_rings[ring_id]; |
| if (!start) { |
| if (VALID_RING(dbg_ring->id)) { |
| /* Initialize the information for the ring */ |
| dbg_ring->state = RING_SUSPEND; |
| dbg_ring->log_level = 0; |
| dbg_ring->rp = dbg_ring->wp = 0; |
| dbg_ring->threshold = 0; |
| memset(&dbg_ring->stat, 0, sizeof(struct ring_statistics)); |
| memset(dbg_ring->ring_buf, 0, dbg_ring->ring_size); |
| } |
| } |
| } |
| return ret; |
| } |
| |
| /* |
| * dhd_dbg_send_urgent_evt: send the health check evt to Upper layer |
| * |
| * Return: An error code or 0 on success. |
| */ |
| |
| int |
| dhd_dbg_send_urgent_evt(dhd_pub_t *dhdp, const void *data, const uint32 len) |
| { |
| dhd_dbg_t *dbg; |
| int ret = BCME_OK; |
| if (!dhdp || !dhdp->dbg) |
| return BCME_BADADDR; |
| |
| dbg = dhdp->dbg; |
| if (dbg->urgent_notifier) { |
| dbg->urgent_notifier(dhdp, data, len); |
| } |
| return ret; |
| } |
| |
| #if defined(DBG_PKT_MON) || defined(DHD_PKT_LOGGING) |
| uint32 |
| __dhd_dbg_pkt_hash(uintptr_t pkt, uint32 pktid) |
| { |
| uint32 __pkt; |
| uint32 __pktid; |
| |
| __pkt = ((int)pkt) >= 0 ? (2 * pkt) : (-2 * pkt - 1); |
| __pktid = ((int)pktid) >= 0 ? (2 * pktid) : (-2 * pktid - 1); |
| |
| return (__pkt >= __pktid ? (__pkt * __pkt + __pkt + __pktid) : |
| (__pkt + __pktid * __pktid)); |
| } |
| |
| #define __TIMESPEC_TO_US(ts) \ |
| (((uint32)(ts).tv_sec * USEC_PER_SEC) + ((ts).tv_nsec / NSEC_PER_USEC)) |
| |
| uint32 |
| __dhd_dbg_driver_ts_usec(void) |
| { |
| struct timespec ts; |
| |
| get_monotonic_boottime(&ts); |
| return ((uint32)(__TIMESPEC_TO_US(ts))); |
| } |
| |
| wifi_tx_packet_fate |
| __dhd_dbg_map_tx_status_to_pkt_fate(uint16 status) |
| { |
| wifi_tx_packet_fate pkt_fate; |
| |
| switch (status) { |
| case WLFC_CTL_PKTFLAG_DISCARD: |
| pkt_fate = TX_PKT_FATE_ACKED; |
| break; |
| case WLFC_CTL_PKTFLAG_D11SUPPRESS: |
| /* intensional fall through */ |
| case WLFC_CTL_PKTFLAG_WLSUPPRESS: |
| pkt_fate = TX_PKT_FATE_FW_QUEUED; |
| break; |
| case WLFC_CTL_PKTFLAG_TOSSED_BYWLC: |
| pkt_fate = TX_PKT_FATE_FW_DROP_INVALID; |
| break; |
| case WLFC_CTL_PKTFLAG_DISCARD_NOACK: |
| pkt_fate = TX_PKT_FATE_SENT; |
| break; |
| default: |
| pkt_fate = TX_PKT_FATE_FW_DROP_OTHER; |
| break; |
| } |
| |
| return pkt_fate; |
| } |
| #endif /* DBG_PKT_MON || DHD_PKT_LOGGING */ |
| |
| #ifdef DBG_PKT_MON |
| static int |
| __dhd_dbg_free_tx_pkts(dhd_pub_t *dhdp, dhd_dbg_tx_info_t *tx_pkts, |
| uint16 pkt_count) |
| { |
| uint16 count; |
| |
| DHD_PKT_INFO(("%s, %d\n", __FUNCTION__, __LINE__)); |
| count = 0; |
| while ((count < pkt_count) && tx_pkts) { |
| if (tx_pkts->info.pkt) |
| PKTFREE(dhdp->osh, tx_pkts->info.pkt, TRUE); |
| tx_pkts++; |
| count++; |
| } |
| |
| return BCME_OK; |
| } |
| |
| static int |
| __dhd_dbg_free_rx_pkts(dhd_pub_t *dhdp, dhd_dbg_rx_info_t *rx_pkts, |
| uint16 pkt_count) |
| { |
| uint16 count; |
| |
| DHD_PKT_INFO(("%s, %d\n", __FUNCTION__, __LINE__)); |
| count = 0; |
| while ((count < pkt_count) && rx_pkts) { |
| if (rx_pkts->info.pkt) |
| PKTFREE(dhdp->osh, rx_pkts->info.pkt, TRUE); |
| rx_pkts++; |
| count++; |
| } |
| |
| return BCME_OK; |
| } |
| |
| void |
| __dhd_dbg_dump_pkt_info(dhd_pub_t *dhdp, dhd_dbg_pkt_info_t *info) |
| { |
| if (DHD_PKT_MON_DUMP_ON()) { |
| DHD_PKT_MON(("payload type = %d\n", info->payload_type)); |
| DHD_PKT_MON(("driver ts = %u\n", info->driver_ts)); |
| DHD_PKT_MON(("firmware ts = %u\n", info->firmware_ts)); |
| DHD_PKT_MON(("packet hash = %u\n", info->pkt_hash)); |
| DHD_PKT_MON(("packet length = %zu\n", info->pkt_len)); |
| DHD_PKT_MON(("packet address = %p\n", info->pkt)); |
| DHD_PKT_MON(("packet data = \n")); |
| if (DHD_PKT_MON_ON()) { |
| prhex(NULL, PKTDATA(dhdp->osh, info->pkt), info->pkt_len); |
| } |
| } |
| } |
| |
| void |
| __dhd_dbg_dump_tx_pkt_info(dhd_pub_t *dhdp, dhd_dbg_tx_info_t *tx_pkt, |
| uint16 count) |
| { |
| if (DHD_PKT_MON_DUMP_ON()) { |
| DHD_PKT_MON(("\nTX (count: %d)\n", ++count)); |
| DHD_PKT_MON(("packet fate = %d\n", tx_pkt->fate)); |
| __dhd_dbg_dump_pkt_info(dhdp, &tx_pkt->info); |
| } |
| } |
| |
| void |
| __dhd_dbg_dump_rx_pkt_info(dhd_pub_t *dhdp, dhd_dbg_rx_info_t *rx_pkt, |
| uint16 count) |
| { |
| if (DHD_PKT_MON_DUMP_ON()) { |
| DHD_PKT_MON(("\nRX (count: %d)\n", ++count)); |
| DHD_PKT_MON(("packet fate = %d\n", rx_pkt->fate)); |
| __dhd_dbg_dump_pkt_info(dhdp, &rx_pkt->info); |
| } |
| } |
| |
| int |
| dhd_dbg_attach_pkt_monitor(dhd_pub_t *dhdp, |
| dbg_mon_tx_pkts_t tx_pkt_mon, |
| dbg_mon_tx_status_t tx_status_mon, |
| dbg_mon_rx_pkts_t rx_pkt_mon) |
| { |
| |
| dhd_dbg_tx_report_t *tx_report = NULL; |
| dhd_dbg_rx_report_t *rx_report = NULL; |
| dhd_dbg_tx_info_t *tx_pkts = NULL; |
| dhd_dbg_rx_info_t *rx_pkts = NULL; |
| dhd_dbg_pkt_mon_state_t tx_pkt_state; |
| dhd_dbg_pkt_mon_state_t tx_status_state; |
| dhd_dbg_pkt_mon_state_t rx_pkt_state; |
| gfp_t kflags; |
| uint32 alloc_len; |
| int ret = BCME_OK; |
| unsigned long flags; |
| |
| DHD_PKT_INFO(("%s, %d\n", __FUNCTION__, __LINE__)); |
| if (!dhdp || !dhdp->dbg) { |
| DHD_PKT_MON(("%s(): dhdp=%p, dhdp->dbg=%p\n", __FUNCTION__, |
| dhdp, (dhdp ? dhdp->dbg : NULL))); |
| return -EINVAL; |
| } |
| |
| DHD_PKT_MON_LOCK(dhdp->dbg->pkt_mon_lock, flags); |
| tx_pkt_state = dhdp->dbg->pkt_mon.tx_pkt_state; |
| tx_status_state = dhdp->dbg->pkt_mon.tx_pkt_state; |
| rx_pkt_state = dhdp->dbg->pkt_mon.rx_pkt_state; |
| |
| if (PKT_MON_ATTACHED(tx_pkt_state) || PKT_MON_ATTACHED(tx_status_state) || |
| PKT_MON_ATTACHED(rx_pkt_state)) { |
| DHD_PKT_MON(("%s(): packet monitor is already attached, " |
| "tx_pkt_state=%d, tx_status_state=%d, rx_pkt_state=%d\n", |
| __FUNCTION__, tx_pkt_state, tx_status_state, rx_pkt_state)); |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| /* return success as the intention was to initialize packet monitor */ |
| return BCME_OK; |
| } |
| |
| kflags = in_atomic() ? GFP_ATOMIC : GFP_KERNEL; |
| |
| /* allocate and initialize tx packet monitoring */ |
| alloc_len = sizeof(*tx_report); |
| tx_report = (dhd_dbg_tx_report_t *)kzalloc(alloc_len, kflags); |
| if (unlikely(!tx_report)) { |
| DHD_ERROR(("%s(): could not allocate memory for - " |
| "dhd_dbg_tx_report_t\n", __FUNCTION__)); |
| ret = -ENOMEM; |
| goto fail; |
| } |
| |
| alloc_len = (sizeof(*tx_pkts) * MAX_FATE_LOG_LEN); |
| tx_pkts = (dhd_dbg_tx_info_t *)kzalloc(alloc_len, kflags); |
| if (unlikely(!tx_pkts)) { |
| DHD_ERROR(("%s(): could not allocate memory for - " |
| "dhd_dbg_tx_info_t\n", __FUNCTION__)); |
| ret = -ENOMEM; |
| goto fail; |
| } |
| dhdp->dbg->pkt_mon.tx_report = tx_report; |
| dhdp->dbg->pkt_mon.tx_report->tx_pkts = tx_pkts; |
| dhdp->dbg->pkt_mon.tx_pkt_mon = tx_pkt_mon; |
| dhdp->dbg->pkt_mon.tx_status_mon = tx_status_mon; |
| dhdp->dbg->pkt_mon.tx_pkt_state = PKT_MON_ATTACHED; |
| dhdp->dbg->pkt_mon.tx_status_state = PKT_MON_ATTACHED; |
| |
| /* allocate and initialze rx packet monitoring */ |
| alloc_len = sizeof(*rx_report); |
| rx_report = (dhd_dbg_rx_report_t *)kzalloc(alloc_len, kflags); |
| if (unlikely(!rx_report)) { |
| DHD_ERROR(("%s(): could not allocate memory for - " |
| "dhd_dbg_rx_report_t\n", __FUNCTION__)); |
| ret = -ENOMEM; |
| goto fail; |
| } |
| |
| alloc_len = (sizeof(*rx_pkts) * MAX_FATE_LOG_LEN); |
| rx_pkts = (dhd_dbg_rx_info_t *)kzalloc(alloc_len, kflags); |
| if (unlikely(!rx_pkts)) { |
| DHD_ERROR(("%s(): could not allocate memory for - " |
| "dhd_dbg_rx_info_t\n", __FUNCTION__)); |
| ret = -ENOMEM; |
| goto fail; |
| } |
| dhdp->dbg->pkt_mon.rx_report = rx_report; |
| dhdp->dbg->pkt_mon.rx_report->rx_pkts = rx_pkts; |
| dhdp->dbg->pkt_mon.rx_pkt_mon = rx_pkt_mon; |
| dhdp->dbg->pkt_mon.rx_pkt_state = PKT_MON_ATTACHED; |
| |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| DHD_PKT_MON(("%s(): packet monitor attach succeeded\n", __FUNCTION__)); |
| return ret; |
| |
| fail: |
| /* tx packet monitoring */ |
| if (tx_pkts) { |
| kfree(tx_pkts); |
| } |
| if (tx_report) { |
| kfree(tx_report); |
| } |
| dhdp->dbg->pkt_mon.tx_report = NULL; |
| dhdp->dbg->pkt_mon.tx_report->tx_pkts = NULL; |
| dhdp->dbg->pkt_mon.tx_pkt_mon = NULL; |
| dhdp->dbg->pkt_mon.tx_status_mon = NULL; |
| dhdp->dbg->pkt_mon.tx_pkt_state = PKT_MON_DETACHED; |
| dhdp->dbg->pkt_mon.tx_status_state = PKT_MON_DETACHED; |
| |
| /* rx packet monitoring */ |
| if (rx_pkts) { |
| kfree(rx_pkts); |
| } |
| if (rx_report) { |
| kfree(rx_report); |
| } |
| dhdp->dbg->pkt_mon.rx_report = NULL; |
| dhdp->dbg->pkt_mon.rx_report->rx_pkts = NULL; |
| dhdp->dbg->pkt_mon.rx_pkt_mon = NULL; |
| dhdp->dbg->pkt_mon.rx_pkt_state = PKT_MON_DETACHED; |
| |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| DHD_ERROR(("%s(): packet monitor attach failed\n", __FUNCTION__)); |
| return ret; |
| } |
| |
| int |
| dhd_dbg_start_pkt_monitor(dhd_pub_t *dhdp) |
| { |
| dhd_dbg_tx_report_t *tx_report; |
| dhd_dbg_rx_report_t *rx_report; |
| dhd_dbg_pkt_mon_state_t tx_pkt_state; |
| dhd_dbg_pkt_mon_state_t tx_status_state; |
| dhd_dbg_pkt_mon_state_t rx_pkt_state; |
| unsigned long flags; |
| |
| DHD_PKT_INFO(("%s, %d\n", __FUNCTION__, __LINE__)); |
| if (!dhdp || !dhdp->dbg) { |
| DHD_PKT_MON(("%s(): dhdp=%p, dhdp->dbg=%p\n", __FUNCTION__, |
| dhdp, (dhdp ? dhdp->dbg : NULL))); |
| return -EINVAL; |
| } |
| |
| DHD_PKT_MON_LOCK(dhdp->dbg->pkt_mon_lock, flags); |
| tx_pkt_state = dhdp->dbg->pkt_mon.tx_pkt_state; |
| tx_status_state = dhdp->dbg->pkt_mon.tx_status_state; |
| rx_pkt_state = dhdp->dbg->pkt_mon.rx_pkt_state; |
| |
| if (PKT_MON_DETACHED(tx_pkt_state) || PKT_MON_DETACHED(tx_status_state) || |
| PKT_MON_DETACHED(rx_pkt_state)) { |
| DHD_PKT_MON(("%s(): packet monitor is not yet enabled, " |
| "tx_pkt_state=%d, tx_status_state=%d, rx_pkt_state=%d\n", |
| __FUNCTION__, tx_pkt_state, tx_status_state, rx_pkt_state)); |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| return -EINVAL; |
| } |
| |
| dhdp->dbg->pkt_mon.tx_pkt_state = PKT_MON_STARTING; |
| dhdp->dbg->pkt_mon.tx_status_state = PKT_MON_STARTING; |
| dhdp->dbg->pkt_mon.rx_pkt_state = PKT_MON_STARTING; |
| |
| tx_report = dhdp->dbg->pkt_mon.tx_report; |
| rx_report = dhdp->dbg->pkt_mon.rx_report; |
| if (!tx_report || !rx_report) { |
| DHD_PKT_MON(("%s(): tx_report=%p, rx_report=%p\n", |
| __FUNCTION__, tx_report, rx_report)); |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| return -EINVAL; |
| } |
| |
| |
| tx_pkt_state = dhdp->dbg->pkt_mon.tx_pkt_state; |
| tx_status_state = dhdp->dbg->pkt_mon.tx_status_state; |
| rx_pkt_state = dhdp->dbg->pkt_mon.rx_pkt_state; |
| |
| /* Safe to free packets as state pkt_state is STARTING */ |
| __dhd_dbg_free_tx_pkts(dhdp, tx_report->tx_pkts, tx_report->pkt_pos); |
| |
| __dhd_dbg_free_rx_pkts(dhdp, rx_report->rx_pkts, rx_report->pkt_pos); |
| |
| /* reset array postion */ |
| tx_report->pkt_pos = 0; |
| tx_report->status_pos = 0; |
| dhdp->dbg->pkt_mon.tx_pkt_state = PKT_MON_STARTED; |
| dhdp->dbg->pkt_mon.tx_status_state = PKT_MON_STARTED; |
| |
| rx_report->pkt_pos = 0; |
| dhdp->dbg->pkt_mon.rx_pkt_state = PKT_MON_STARTED; |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| |
| DHD_PKT_MON(("%s(): packet monitor started\n", __FUNCTION__)); |
| return BCME_OK; |
| } |
| |
| int |
| dhd_dbg_monitor_tx_pkts(dhd_pub_t *dhdp, void *pkt, uint32 pktid) |
| { |
| dhd_dbg_tx_report_t *tx_report; |
| dhd_dbg_tx_info_t *tx_pkts; |
| dhd_dbg_pkt_mon_state_t tx_pkt_state; |
| uint32 pkt_hash, driver_ts; |
| uint16 pkt_pos; |
| unsigned long flags; |
| |
| if (!dhdp || !dhdp->dbg) { |
| DHD_PKT_MON(("%s(): dhdp=%p, dhdp->dbg=%p\n", __FUNCTION__, |
| dhdp, (dhdp ? dhdp->dbg : NULL))); |
| return -EINVAL; |
| } |
| |
| DHD_PKT_MON_LOCK(dhdp->dbg->pkt_mon_lock, flags); |
| tx_pkt_state = dhdp->dbg->pkt_mon.tx_pkt_state; |
| if (PKT_MON_STARTED(tx_pkt_state)) { |
| tx_report = dhdp->dbg->pkt_mon.tx_report; |
| pkt_pos = tx_report->pkt_pos; |
| |
| if (!PKT_MON_PKT_FULL(pkt_pos)) { |
| tx_pkts = tx_report->tx_pkts; |
| pkt_hash = __dhd_dbg_pkt_hash((uintptr_t)pkt, pktid); |
| driver_ts = __dhd_dbg_driver_ts_usec(); |
| |
| tx_pkts[pkt_pos].info.pkt = PKTDUP(dhdp->osh, pkt); |
| tx_pkts[pkt_pos].info.pkt_len = PKTLEN(dhdp->osh, pkt); |
| tx_pkts[pkt_pos].info.pkt_hash = pkt_hash; |
| tx_pkts[pkt_pos].info.driver_ts = driver_ts; |
| tx_pkts[pkt_pos].info.firmware_ts = 0U; |
| tx_pkts[pkt_pos].info.payload_type = FRAME_TYPE_ETHERNET_II; |
| tx_pkts[pkt_pos].fate = TX_PKT_FATE_DRV_QUEUED; |
| |
| tx_report->pkt_pos++; |
| } else { |
| dhdp->dbg->pkt_mon.tx_pkt_state = PKT_MON_STOPPED; |
| DHD_PKT_MON(("%s(): tx pkt logging stopped, reached " |
| "max limit\n", __FUNCTION__)); |
| } |
| } |
| |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| return BCME_OK; |
| } |
| |
| int |
| dhd_dbg_monitor_tx_status(dhd_pub_t *dhdp, void *pkt, uint32 pktid, |
| uint16 status) |
| { |
| dhd_dbg_tx_report_t *tx_report; |
| dhd_dbg_tx_info_t *tx_pkt; |
| dhd_dbg_pkt_mon_state_t tx_status_state; |
| wifi_tx_packet_fate pkt_fate; |
| uint32 pkt_hash, temp_hash; |
| uint16 pkt_pos, status_pos; |
| int16 count; |
| bool found = FALSE; |
| unsigned long flags; |
| |
| if (!dhdp || !dhdp->dbg) { |
| DHD_PKT_MON(("%s(): dhdp=%p, dhdp->dbg=%p\n", __FUNCTION__, |
| dhdp, (dhdp ? dhdp->dbg : NULL))); |
| return -EINVAL; |
| } |
| |
| DHD_PKT_MON_LOCK(dhdp->dbg->pkt_mon_lock, flags); |
| tx_status_state = dhdp->dbg->pkt_mon.tx_status_state; |
| if (PKT_MON_STARTED(tx_status_state)) { |
| tx_report = dhdp->dbg->pkt_mon.tx_report; |
| pkt_pos = tx_report->pkt_pos; |
| status_pos = tx_report->status_pos; |
| |
| if (!PKT_MON_STATUS_FULL(pkt_pos, status_pos)) { |
| pkt_hash = __dhd_dbg_pkt_hash((uintptr_t)pkt, pktid); |
| pkt_fate = __dhd_dbg_map_tx_status_to_pkt_fate(status); |
| |
| /* best bet (in-order tx completion) */ |
| count = status_pos; |
| tx_pkt = (((dhd_dbg_tx_info_t *)tx_report->tx_pkts) + status_pos); |
| while ((count < pkt_pos) && tx_pkt) { |
| temp_hash = tx_pkt->info.pkt_hash; |
| if (temp_hash == pkt_hash) { |
| tx_pkt->fate = pkt_fate; |
| tx_report->status_pos++; |
| found = TRUE; |
| break; |
| } |
| tx_pkt++; |
| count++; |
| } |
| |
| /* search until beginning (handles out-of-order completion) */ |
| if (!found) { |
| count = status_pos - 1; |
| tx_pkt = (((dhd_dbg_tx_info_t *)tx_report->tx_pkts) + count); |
| while ((count >= 0) && tx_pkt) { |
| temp_hash = tx_pkt->info.pkt_hash; |
| if (temp_hash == pkt_hash) { |
| tx_pkt->fate = pkt_fate; |
| tx_report->status_pos++; |
| found = TRUE; |
| break; |
| } |
| tx_pkt--; |
| count--; |
| } |
| |
| if (!found) { |
| /* still couldn't match tx_status */ |
| DHD_ERROR(("%s(): couldn't match tx_status, pkt_pos=%u, " |
| "status_pos=%u, pkt_fate=%u\n", __FUNCTION__, |
| pkt_pos, status_pos, pkt_fate)); |
| } |
| } |
| } else { |
| dhdp->dbg->pkt_mon.tx_status_state = PKT_MON_STOPPED; |
| DHD_PKT_MON(("%s(): tx_status logging stopped, reached " |
| "max limit\n", __FUNCTION__)); |
| } |
| } |
| |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| return BCME_OK; |
| } |
| |
| int |
| dhd_dbg_monitor_rx_pkts(dhd_pub_t *dhdp, void *pkt) |
| { |
| dhd_dbg_rx_report_t *rx_report; |
| dhd_dbg_rx_info_t *rx_pkts; |
| dhd_dbg_pkt_mon_state_t rx_pkt_state; |
| uint32 driver_ts; |
| uint16 pkt_pos; |
| unsigned long flags; |
| |
| if (!dhdp || !dhdp->dbg) { |
| DHD_PKT_MON(("%s(): dhdp=%p, dhdp->dbg=%p\n", __FUNCTION__, |
| dhdp, (dhdp ? dhdp->dbg : NULL))); |
| return -EINVAL; |
| } |
| |
| DHD_PKT_MON_LOCK(dhdp->dbg->pkt_mon_lock, flags); |
| rx_pkt_state = dhdp->dbg->pkt_mon.rx_pkt_state; |
| if (PKT_MON_STARTED(rx_pkt_state)) { |
| rx_report = dhdp->dbg->pkt_mon.rx_report; |
| pkt_pos = rx_report->pkt_pos; |
| |
| if (!PKT_MON_PKT_FULL(pkt_pos)) { |
| rx_pkts = rx_report->rx_pkts; |
| driver_ts = __dhd_dbg_driver_ts_usec(); |
| |
| rx_pkts[pkt_pos].info.pkt = PKTDUP(dhdp->osh, pkt); |
| rx_pkts[pkt_pos].info.pkt_len = PKTLEN(dhdp->osh, pkt); |
| rx_pkts[pkt_pos].info.pkt_hash = 0U; |
| rx_pkts[pkt_pos].info.driver_ts = driver_ts; |
| rx_pkts[pkt_pos].info.firmware_ts = 0U; |
| rx_pkts[pkt_pos].info.payload_type = FRAME_TYPE_ETHERNET_II; |
| rx_pkts[pkt_pos].fate = RX_PKT_FATE_SUCCESS; |
| |
| rx_report->pkt_pos++; |
| } else { |
| dhdp->dbg->pkt_mon.rx_pkt_state = PKT_MON_STOPPED; |
| DHD_PKT_MON(("%s(): rx pkt logging stopped, reached " |
| "max limit\n", __FUNCTION__)); |
| } |
| } |
| |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| return BCME_OK; |
| } |
| |
| int |
| dhd_dbg_stop_pkt_monitor(dhd_pub_t *dhdp) |
| { |
| dhd_dbg_pkt_mon_state_t tx_pkt_state; |
| dhd_dbg_pkt_mon_state_t tx_status_state; |
| dhd_dbg_pkt_mon_state_t rx_pkt_state; |
| unsigned long flags; |
| |
| DHD_PKT_INFO(("%s, %d\n", __FUNCTION__, __LINE__)); |
| if (!dhdp || !dhdp->dbg) { |
| DHD_PKT_MON(("%s(): dhdp=%p, dhdp->dbg=%p\n", __FUNCTION__, |
| dhdp, (dhdp ? dhdp->dbg : NULL))); |
| return -EINVAL; |
| } |
| |
| DHD_PKT_MON_LOCK(dhdp->dbg->pkt_mon_lock, flags); |
| tx_pkt_state = dhdp->dbg->pkt_mon.tx_pkt_state; |
| tx_status_state = dhdp->dbg->pkt_mon.tx_status_state; |
| rx_pkt_state = dhdp->dbg->pkt_mon.rx_pkt_state; |
| |
| if (PKT_MON_DETACHED(tx_pkt_state) || PKT_MON_DETACHED(tx_status_state) || |
| PKT_MON_DETACHED(rx_pkt_state)) { |
| DHD_PKT_MON(("%s(): packet monitor is not yet enabled, " |
| "tx_pkt_state=%d, tx_status_state=%d, rx_pkt_state=%d\n", |
| __FUNCTION__, tx_pkt_state, tx_status_state, rx_pkt_state)); |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| return -EINVAL; |
| } |
| dhdp->dbg->pkt_mon.tx_pkt_state = PKT_MON_STOPPED; |
| dhdp->dbg->pkt_mon.tx_status_state = PKT_MON_STOPPED; |
| dhdp->dbg->pkt_mon.rx_pkt_state = PKT_MON_STOPPED; |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| |
| DHD_PKT_MON(("%s(): packet monitor stopped\n", __FUNCTION__)); |
| return BCME_OK; |
| } |
| |
| #define __COPY_TO_USER(to, from, n) \ |
| do { \ |
| int __ret; \ |
| __ret = copy_to_user((void __user *)(to), (void *)(from), \ |
| (unsigned long)(n)); \ |
| if (unlikely(__ret)) { \ |
| DHD_ERROR(("%s():%d: copy_to_user failed, ret=%d\n", \ |
| __FUNCTION__, __LINE__, __ret)); \ |
| return __ret; \ |
| } \ |
| } while (0); |
| |
| int |
| dhd_dbg_monitor_get_tx_pkts(dhd_pub_t *dhdp, void __user *user_buf, |
| uint16 req_count, uint16 *resp_count) |
| { |
| dhd_dbg_tx_report_t *tx_report; |
| dhd_dbg_tx_info_t *tx_pkt; |
| wifi_tx_report_t *ptr; |
| compat_wifi_tx_report_t *cptr; |
| dhd_dbg_pkt_mon_state_t tx_pkt_state; |
| dhd_dbg_pkt_mon_state_t tx_status_state; |
| uint16 pkt_count, count; |
| unsigned long flags; |
| |
| DHD_PKT_INFO(("%s, %d\n", __FUNCTION__, __LINE__)); |
| BCM_REFERENCE(ptr); |
| BCM_REFERENCE(cptr); |
| |
| if (!dhdp || !dhdp->dbg) { |
| DHD_PKT_MON(("%s(): dhdp=%p, dhdp->dbg=%p\n", __FUNCTION__, |
| dhdp, (dhdp ? dhdp->dbg : NULL))); |
| return -EINVAL; |
| } |
| |
| DHD_PKT_MON_LOCK(dhdp->dbg->pkt_mon_lock, flags); |
| tx_pkt_state = dhdp->dbg->pkt_mon.tx_pkt_state; |
| tx_status_state = dhdp->dbg->pkt_mon.tx_status_state; |
| if (PKT_MON_DETACHED(tx_pkt_state) || PKT_MON_DETACHED(tx_status_state)) { |
| DHD_PKT_MON(("%s(): packet monitor is not yet enabled, " |
| "tx_pkt_state=%d, tx_status_state=%d\n", __FUNCTION__, |
| tx_pkt_state, tx_status_state)); |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| return -EINVAL; |
| } |
| |
| count = 0; |
| tx_report = dhdp->dbg->pkt_mon.tx_report; |
| tx_pkt = tx_report->tx_pkts; |
| pkt_count = MIN(req_count, tx_report->status_pos); |
| |
| #ifdef CONFIG_COMPAT |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0)) |
| if (in_compat_syscall()) |
| #else |
| if (is_compat_task()) |
| #endif |
| { |
| cptr = (compat_wifi_tx_report_t *)user_buf; |
| while ((count < pkt_count) && tx_pkt && cptr) { |
| compat_wifi_tx_report_t *comp_ptr = compat_ptr((uintptr_t) cptr); |
| compat_dhd_dbg_pkt_info_t compat_tx_pkt; |
| __dhd_dbg_dump_tx_pkt_info(dhdp, tx_pkt, count); |
| __COPY_TO_USER(&comp_ptr->fate, &tx_pkt->fate, sizeof(tx_pkt->fate)); |
| |
| compat_tx_pkt.payload_type = tx_pkt->info.payload_type; |
| compat_tx_pkt.pkt_len = tx_pkt->info.pkt_len; |
| compat_tx_pkt.driver_ts = tx_pkt->info.driver_ts; |
| compat_tx_pkt.firmware_ts = tx_pkt->info.firmware_ts; |
| compat_tx_pkt.pkt_hash = tx_pkt->info.pkt_hash; |
| __COPY_TO_USER(&comp_ptr->frame_inf.payload_type, |
| &compat_tx_pkt.payload_type, |
| OFFSETOF(compat_dhd_dbg_pkt_info_t, pkt_hash)); |
| __COPY_TO_USER(comp_ptr->frame_inf.frame_content.ethernet_ii, |
| PKTDATA(dhdp->osh, tx_pkt->info.pkt), tx_pkt->info.pkt_len); |
| |
| cptr++; |
| tx_pkt++; |
| count++; |
| } |
| } else |
| #endif /* CONFIG_COMPAT */ |
| |
| { |
| ptr = (wifi_tx_report_t *)user_buf; |
| while ((count < pkt_count) && tx_pkt && ptr) { |
| __dhd_dbg_dump_tx_pkt_info(dhdp, tx_pkt, count); |
| __COPY_TO_USER(&ptr->fate, &tx_pkt->fate, sizeof(tx_pkt->fate)); |
| __COPY_TO_USER(&ptr->frame_inf.payload_type, |
| &tx_pkt->info.payload_type, |
| OFFSETOF(dhd_dbg_pkt_info_t, pkt_hash)); |
| __COPY_TO_USER(ptr->frame_inf.frame_content.ethernet_ii, |
| PKTDATA(dhdp->osh, tx_pkt->info.pkt), tx_pkt->info.pkt_len); |
| |
| ptr++; |
| tx_pkt++; |
| count++; |
| } |
| } |
| *resp_count = pkt_count; |
| |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| if (!pkt_count) { |
| DHD_ERROR(("%s(): no tx_status in tx completion messages, " |
| "make sure that 'd11status' is enabled in firmware, " |
| "status_pos=%u\n", __FUNCTION__, pkt_count)); |
| } |
| |
| return BCME_OK; |
| } |
| |
| int |
| dhd_dbg_monitor_get_rx_pkts(dhd_pub_t *dhdp, void __user *user_buf, |
| uint16 req_count, uint16 *resp_count) |
| { |
| dhd_dbg_rx_report_t *rx_report; |
| dhd_dbg_rx_info_t *rx_pkt; |
| wifi_rx_report_t *ptr; |
| compat_wifi_rx_report_t *cptr; |
| dhd_dbg_pkt_mon_state_t rx_pkt_state; |
| uint16 pkt_count, count; |
| unsigned long flags; |
| |
| DHD_PKT_INFO(("%s, %d\n", __FUNCTION__, __LINE__)); |
| BCM_REFERENCE(ptr); |
| BCM_REFERENCE(cptr); |
| |
| if (!dhdp || !dhdp->dbg) { |
| DHD_PKT_MON(("%s(): dhdp=%p, dhdp->dbg=%p\n", __FUNCTION__, |
| dhdp, (dhdp ? dhdp->dbg : NULL))); |
| return -EINVAL; |
| } |
| |
| DHD_PKT_MON_LOCK(dhdp->dbg->pkt_mon_lock, flags); |
| rx_pkt_state = dhdp->dbg->pkt_mon.rx_pkt_state; |
| if (PKT_MON_DETACHED(rx_pkt_state)) { |
| DHD_PKT_MON(("%s(): packet fetch is not allowed , " |
| "rx_pkt_state=%d\n", __FUNCTION__, rx_pkt_state)); |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| return -EINVAL; |
| } |
| |
| count = 0; |
| rx_report = dhdp->dbg->pkt_mon.rx_report; |
| rx_pkt = rx_report->rx_pkts; |
| pkt_count = MIN(req_count, rx_report->pkt_pos); |
| |
| #ifdef CONFIG_COMPAT |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0)) |
| if (in_compat_syscall()) |
| #else |
| if (is_compat_task()) |
| #endif |
| { |
| cptr = (compat_wifi_rx_report_t *)user_buf; |
| while ((count < pkt_count) && rx_pkt && cptr) { |
| compat_wifi_rx_report_t *comp_ptr = compat_ptr((uintptr_t) cptr); |
| compat_dhd_dbg_pkt_info_t compat_rx_pkt; |
| __dhd_dbg_dump_rx_pkt_info(dhdp, rx_pkt, count); |
| __COPY_TO_USER(&comp_ptr->fate, &rx_pkt->fate, sizeof(rx_pkt->fate)); |
| |
| compat_rx_pkt.payload_type = rx_pkt->info.payload_type; |
| compat_rx_pkt.pkt_len = rx_pkt->info.pkt_len; |
| compat_rx_pkt.driver_ts = rx_pkt->info.driver_ts; |
| compat_rx_pkt.firmware_ts = rx_pkt->info.firmware_ts; |
| compat_rx_pkt.pkt_hash = rx_pkt->info.pkt_hash; |
| __COPY_TO_USER(&comp_ptr->frame_inf.payload_type, |
| &compat_rx_pkt.payload_type, |
| OFFSETOF(compat_dhd_dbg_pkt_info_t, pkt_hash)); |
| __COPY_TO_USER(comp_ptr->frame_inf.frame_content.ethernet_ii, |
| PKTDATA(dhdp->osh, rx_pkt->info.pkt), rx_pkt->info.pkt_len); |
| |
| cptr++; |
| rx_pkt++; |
| count++; |
| } |
| } else |
| #endif /* CONFIG_COMPAT */ |
| { |
| ptr = (wifi_rx_report_t *)user_buf; |
| while ((count < pkt_count) && rx_pkt && ptr) { |
| __dhd_dbg_dump_rx_pkt_info(dhdp, rx_pkt, count); |
| |
| __COPY_TO_USER(&ptr->fate, &rx_pkt->fate, sizeof(rx_pkt->fate)); |
| __COPY_TO_USER(&ptr->frame_inf.payload_type, |
| &rx_pkt->info.payload_type, |
| OFFSETOF(dhd_dbg_pkt_info_t, pkt_hash)); |
| __COPY_TO_USER(ptr->frame_inf.frame_content.ethernet_ii, |
| PKTDATA(dhdp->osh, rx_pkt->info.pkt), rx_pkt->info.pkt_len); |
| |
| ptr++; |
| rx_pkt++; |
| count++; |
| } |
| } |
| |
| *resp_count = pkt_count; |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| |
| return BCME_OK; |
| } |
| |
| int |
| dhd_dbg_detach_pkt_monitor(dhd_pub_t *dhdp) |
| { |
| dhd_dbg_tx_report_t *tx_report; |
| dhd_dbg_rx_report_t *rx_report; |
| dhd_dbg_pkt_mon_state_t tx_pkt_state; |
| dhd_dbg_pkt_mon_state_t tx_status_state; |
| dhd_dbg_pkt_mon_state_t rx_pkt_state; |
| unsigned long flags; |
| |
| DHD_PKT_INFO(("%s, %d\n", __FUNCTION__, __LINE__)); |
| if (!dhdp || !dhdp->dbg) { |
| DHD_PKT_MON(("%s(): dhdp=%p, dhdp->dbg=%p\n", __FUNCTION__, |
| dhdp, (dhdp ? dhdp->dbg : NULL))); |
| return -EINVAL; |
| } |
| |
| DHD_PKT_MON_LOCK(dhdp->dbg->pkt_mon_lock, flags); |
| tx_pkt_state = dhdp->dbg->pkt_mon.tx_pkt_state; |
| tx_status_state = dhdp->dbg->pkt_mon.tx_status_state; |
| rx_pkt_state = dhdp->dbg->pkt_mon.rx_pkt_state; |
| |
| if (PKT_MON_DETACHED(tx_pkt_state) || PKT_MON_DETACHED(tx_status_state) || |
| PKT_MON_DETACHED(rx_pkt_state)) { |
| DHD_PKT_MON(("%s(): packet monitor is already detached, " |
| "tx_pkt_state=%d, tx_status_state=%d, rx_pkt_state=%d\n", |
| __FUNCTION__, tx_pkt_state, tx_status_state, rx_pkt_state)); |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| return -EINVAL; |
| } |
| |
| tx_report = dhdp->dbg->pkt_mon.tx_report; |
| rx_report = dhdp->dbg->pkt_mon.rx_report; |
| |
| /* free and de-initalize tx packet monitoring */ |
| dhdp->dbg->pkt_mon.tx_pkt_state = PKT_MON_DETACHED; |
| dhdp->dbg->pkt_mon.tx_status_state = PKT_MON_DETACHED; |
| if (tx_report) { |
| if (tx_report->tx_pkts) { |
| __dhd_dbg_free_tx_pkts(dhdp, tx_report->tx_pkts, |
| tx_report->pkt_pos); |
| kfree(tx_report->tx_pkts); |
| dhdp->dbg->pkt_mon.tx_report->tx_pkts = NULL; |
| } |
| kfree(tx_report); |
| dhdp->dbg->pkt_mon.tx_report = NULL; |
| } |
| dhdp->dbg->pkt_mon.tx_pkt_mon = NULL; |
| dhdp->dbg->pkt_mon.tx_status_mon = NULL; |
| |
| /* free and de-initalize rx packet monitoring */ |
| dhdp->dbg->pkt_mon.rx_pkt_state = PKT_MON_DETACHED; |
| if (rx_report) { |
| if (rx_report->rx_pkts) { |
| __dhd_dbg_free_rx_pkts(dhdp, rx_report->rx_pkts, |
| rx_report->pkt_pos); |
| kfree(rx_report->rx_pkts); |
| dhdp->dbg->pkt_mon.rx_report->rx_pkts = NULL; |
| } |
| kfree(rx_report); |
| dhdp->dbg->pkt_mon.rx_report = NULL; |
| } |
| dhdp->dbg->pkt_mon.rx_pkt_mon = NULL; |
| |
| DHD_PKT_MON_UNLOCK(dhdp->dbg->pkt_mon_lock, flags); |
| DHD_PKT_MON(("%s(): packet monitor detach succeeded\n", __FUNCTION__)); |
| return BCME_OK; |
| } |
| #endif /* DBG_PKT_MON */ |
| |
| /* |
| * dhd_dbg_attach: initialziation of dhd dbugability module |
| * |
| * Return: An error code or 0 on success. |
| */ |
| int |
| dhd_dbg_attach(dhd_pub_t *dhdp, dbg_pullreq_t os_pullreq, |
| dbg_urgent_noti_t os_urgent_notifier, void *os_priv) |
| { |
| dhd_dbg_t *dbg; |
| int ret, ring_id; |
| |
| dbg = MALLOCZ(dhdp->osh, sizeof(dhd_dbg_t)); |
| if (!dbg) |
| return BCME_NOMEM; |
| |
| ret = dhd_dbg_ring_init(dhdp, &dbg->dbg_rings[FW_VERBOSE_RING_ID], FW_VERBOSE_RING_ID, |
| (uint8 *)FW_VERBOSE_RING_NAME, FW_VERBOSE_RING_SIZE, DHD_PREALLOC_FW_VERBOSE_RING); |
| if (ret) |
| goto error; |
| |
| ret = dhd_dbg_ring_init(dhdp, &dbg->dbg_rings[FW_EVENT_RING_ID], FW_EVENT_RING_ID, |
| (uint8 *)FW_EVENT_RING_NAME, FW_EVENT_RING_SIZE, DHD_PREALLOC_FW_EVENT_RING); |
| if (ret) |
| goto error; |
| |
| ret = dhd_dbg_ring_init(dhdp, &dbg->dbg_rings[DHD_EVENT_RING_ID], DHD_EVENT_RING_ID, |
| (uint8 *)DHD_EVENT_RING_NAME, DHD_EVENT_RING_SIZE, DHD_PREALLOC_DHD_EVENT_RING); |
| if (ret) |
| goto error; |
| |
| ret = dhd_dbg_ring_init(dhdp, &dbg->dbg_rings[NAN_EVENT_RING_ID], NAN_EVENT_RING_ID, |
| (uint8 *)NAN_EVENT_RING_NAME, NAN_EVENT_RING_SIZE, DHD_PREALLOC_NAN_EVENT_RING); |
| if (ret) |
| goto error; |
| |
| dbg->private = os_priv; |
| dbg->pullreq = os_pullreq; |
| dbg->urgent_notifier = os_urgent_notifier; |
| dhdp->dbg = dbg; |
| |
| return BCME_OK; |
| |
| error: |
| for (ring_id = DEBUG_RING_ID_INVALID + 1; ring_id < DEBUG_RING_ID_MAX; ring_id++) { |
| if (VALID_RING(dbg->dbg_rings[ring_id].id)) { |
| dhd_dbg_ring_deinit(dhdp, &dbg->dbg_rings[ring_id]); |
| } |
| } |
| MFREE(dhdp->osh, dhdp->dbg, sizeof(dhd_dbg_t)); |
| |
| return ret; |
| } |
| |
| /* |
| * dhd_dbg_detach: clean up dhd dbugability module |
| */ |
| void |
| dhd_dbg_detach(dhd_pub_t *dhdp) |
| { |
| int ring_id; |
| dhd_dbg_t *dbg; |
| if (!dhdp->dbg) |
| return; |
| dbg = dhdp->dbg; |
| for (ring_id = DEBUG_RING_ID_INVALID + 1; ring_id < DEBUG_RING_ID_MAX; ring_id++) { |
| if (VALID_RING(dbg->dbg_rings[ring_id].id)) { |
| dhd_dbg_ring_deinit(dhdp, &dbg->dbg_rings[ring_id]); |
| } |
| } |
| MFREE(dhdp->osh, dhdp->dbg, sizeof(dhd_dbg_t)); |
| } |