| /* |
| * DHD debugability packet logging support |
| * |
| * Copyright (C) 2022, 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 <bcmstdlib_s.h> |
| #include <dngl_stats.h> |
| #include <dhd.h> |
| #include <dhd_dbg.h> |
| #include <dhd_pktlog.h> |
| #include <dhd_wlfc.h> |
| #include <dhd_debug.h> |
| |
| #ifdef DHD_COMPACT_PKT_LOG |
| #include <bcmip.h> |
| #include <bcmudp.h> |
| #include <bcmdhcp.h> |
| #include <bcmarp.h> |
| #include <bcmicmp.h> |
| #include <bcmtlv.h> |
| #include <802.11.h> |
| #include <eap.h> |
| #include <eapol.h> |
| #include <bcmendian.h> |
| #include <bcm_l2_filter.h> |
| #include <dhd_bitpack.h> |
| #include <bcmipv6.h> |
| #endif /* DHD_COMPACT_PKT_LOG */ |
| |
| #ifdef DHD_PKT_LOGGING |
| #ifndef strtoul |
| #define strtoul(nptr, endptr, base) bcm_strtoul((nptr), (endptr), (base)) |
| #endif /* strtoul */ |
| extern int wl_pattern_atoh(char *src, char *dst); |
| extern int pattern_atoh_len(char *src, char *dst, int len); |
| extern wifi_tx_packet_fate __dhd_dbg_map_tx_status_to_pkt_fate(uint16 status); |
| |
| #ifdef DHD_COMPACT_PKT_LOG |
| #define CPKT_LOG_BITS_PER_BYTE 8 |
| |
| #define CPKT_LOG_BIT_LEN_TYPE 4 |
| |
| #define CPKT_LOG_BIT_OFFSET_TS 0 |
| #define CPKT_LOG_BIT_OFFSET_DIR 5 |
| #define CPKT_LOG_BIT_OFFSET_TYPE 6 |
| #define CPKT_LOG_BIT_OFFSET_SUBTYPE 10 |
| #define CPKT_LOG_BIT_OFFSET_PKT_FATE 18 |
| |
| #define CPKT_LOG_BIT_MASK_TS 0x1f |
| #define CPKT_LOG_BIT_MASK_DIR 0x01 |
| #define CPKT_LOG_BIT_MASK_TYPE 0x0f |
| #define CPKT_LOG_BIT_MASK_SUBTYPE 0xff |
| #define CPKT_LOG_BIT_MASK_PKT_FATE 0x0f |
| |
| #define CPKT_LOG_DNS_PORT_CLIENT 53 |
| #define CPKT_LOG_MDNS_PORT_CLIENT 5353 |
| |
| #define CPKT_LOG_TYPE_DNS 0x0 |
| #define CPKT_LOG_TYPE_ARP 0x1 |
| #define CPKT_LOG_TYPE_ICMP_REQ 0x2 |
| #define CPKT_LOG_TYPE_ICMP_RES 0x3 |
| #define CPKT_LOG_TYPE_ICMP_UNREACHABLE 0x4 |
| #define CPKT_LOG_TYPE_DHCP 0x5 |
| #define CPKT_LOG_TYPE_802_1X 0x6 |
| #define CPKT_LOG_TYPE_ICMPv6 0x7 |
| #define CPKT_LOG_TYPE_OTHERS 0xf |
| |
| #define CPKT_LOG_802_1X_SUBTYPE_IDENTITY 0x0 |
| #define CPKT_LOG_802_1X_SUBTYPE_TLS 0x1 |
| #define CPKT_LOG_802_1X_SUBTYPE_TTLS 0x2 |
| #define CPKT_LOG_802_1X_SUBTYPE_PEAP 0x3 |
| #define CPKT_LOG_802_1X_SUBTYPE_FAST 0x4 |
| #define CPKT_LOG_802_1X_SUBTYPE_LEAP 0x5 |
| #define CPKT_LOG_802_1X_SUBTYPE_PWD 0x6 |
| #define CPKT_LOG_802_1X_SUBTYPE_SIM 0x7 |
| #define CPKT_LOG_802_1X_SUBTYPE_AKA 0x8 |
| #define CPKT_LOG_802_1X_SUBTYPE_AKAP 0x9 |
| #define CPKT_LOG_802_1X_SUBTYPE_SUCCESS 0xA |
| #define CPKT_LOG_802_1X_SUBTYPE_4WAY_M1 0xB |
| #define CPKT_LOG_802_1X_SUBTYPE_4WAY_M2 0xC |
| #define CPKT_LOG_802_1X_SUBTYPE_4WAY_M3 0xD |
| #define CPKT_LOG_802_1X_SUBTYPE_4WAY_M4 0xE |
| #define CPKT_LOG_802_1X_SUBTYPE_OTHERS 0xF |
| |
| #define CPKT_LOG_DHCP_MAGIC_COOKIE_LEN 4 |
| |
| #define CPKT_LOG_ICMP_TYPE_DEST_UNREACHABLE 3 |
| #define CPKT_LOG_ICMP_TYPE_DEST_UNREACHABLE_IPV4_OFFSET 4 |
| |
| typedef struct dhd_cpkt_log_ts_node { |
| struct rb_node rb; |
| |
| uint64 ts_diff; /* key, usec */ |
| int idx; |
| } dhd_cpkt_log_ts_node_t; |
| |
| /* Compact Packet Log Timestamp values, unit: uSec */ |
| const uint64 dhd_cpkt_log_tt_idx[] = { |
| 10000, 50000, 100000, 150000, 300000, 500000, 750000, 1000000, 3000000, 5000000, 7500000, |
| 10000000, 12500000, 15000000, 17500000, 20000000, 22500000, 25000000, 27500000, 30000000, |
| 32500000, 35000000, 37500000, 40000000, 50000000, 75000000, 150000000, 300000000, 400000000, |
| 500000000, 600000000 |
| }; |
| #define CPKT_LOG_TT_IDX_ARR_SZ ARRAYSIZE(dhd_cpkt_log_tt_idx) |
| |
| static int dhd_cpkt_log_init_tt(dhd_pub_t *dhdp); |
| static void dhd_cpkt_log_deinit_tt(dhd_pub_t *dhdp); |
| #endif /* DHD_COMPACT_PKT_LOG */ |
| |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| #define DHD_PKT_LOGGING_DBGRING_MAX_SIZE 400u |
| const char dhd_eap_pattern[] = "12 0xffff 0x888e"; |
| const char dhd_arp_pattern[] = "12 0xffff 0x0806"; |
| const char dhd_icmp_pattern[] = |
| "12 0xffffff0000000000000000ff 0x080045000000000000000001"; |
| const char dhd_icmpv6_pattern[] = |
| "12 0xfffff00000000000ff 0x86dd6000000000003a"; |
| const char dhd_dhcp_pattern_01[] = |
| "12 0xffffff0000000000000000ff00000000000000000000ffffffff" |
| " 0x0800450000000000000000110000000000000000000000430044"; |
| const char dhd_dhcp_pattern_02[] = |
| "12 0xffffff0000000000000000ff00000000000000000000ffffffff" |
| " 0x0800450000000000000000110000000000000000000000440043"; |
| const char dhd_dns_pattern_01[] = |
| "12 0xffffff0000000000000000ff000000000000000000000000ffff" |
| " 0x0800450000000000000000110000000000000000000000000035"; |
| const char dhd_dns_pattern_02[] = |
| "12 0xffffff0000000000000000ff00000000000000000000ffff0000" |
| " 0x0800450000000000000000110000000000000000000000350000"; |
| const char dhd_dns_pattern_03[] = |
| "12 0xffffff0000000000000000ff00000000000000000000ffffffff" |
| " 0x0800450000000000000000110000000000000000000014e914e9"; |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| |
| int |
| dhd_os_attach_pktlog(dhd_pub_t *dhdp) |
| { |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| char filter_pattern[MAX_FILTER_PATTERN_LEN]; |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| dhd_pktlog_t *pktlog; |
| |
| if (!dhdp) { |
| DHD_ERROR(("%s(): dhdp is NULL\n", __FUNCTION__)); |
| return -EINVAL; |
| } |
| |
| pktlog = (dhd_pktlog_t *)VMALLOCZ(dhdp->osh, sizeof(dhd_pktlog_t)); |
| if (unlikely(!pktlog)) { |
| DHD_ERROR(("%s(): could not allocate memory for - " |
| "dhd_pktlog_t\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| dhdp->pktlog = pktlog; |
| pktlog->dhdp = dhdp; |
| |
| OSL_ATOMIC_INIT(dhdp->osh, &pktlog->pktlog_status); |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| OSL_ATOMIC_INIT(dhdp->osh, &pktlog->enable); |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| |
| /* pktlog ring */ |
| dhdp->pktlog->pktlog_ring = dhd_pktlog_ring_init(dhdp, MIN_PKTLOG_LEN); |
| dhdp->pktlog->pktlog_filter = dhd_pktlog_filter_init(MAX_DHD_PKTLOG_FILTER_LEN); |
| #ifdef DHD_COMPACT_PKT_LOG |
| dhd_cpkt_log_init_tt(dhdp); |
| #endif |
| |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| /* By default, EAP, ARP, ICMP, ICMPV6, DHCP packets are allowed. */ |
| DHD_PKTLOG_FILTER_ADD(dhd_eap_pattern, filter_pattern, dhdp); |
| DHD_PKTLOG_FILTER_ADD(dhd_arp_pattern, filter_pattern, dhdp); |
| DHD_PKTLOG_FILTER_ADD(dhd_icmp_pattern, filter_pattern, dhdp); |
| DHD_PKTLOG_FILTER_ADD(dhd_icmpv6_pattern, filter_pattern, dhdp); |
| DHD_PKTLOG_FILTER_ADD(dhd_dhcp_pattern_01, filter_pattern, dhdp); |
| DHD_PKTLOG_FILTER_ADD(dhd_dhcp_pattern_02, filter_pattern, dhdp); |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| |
| DHD_ERROR(("%s(): dhd_os_attach_pktlog attach\n", __FUNCTION__)); |
| |
| return BCME_OK; |
| } |
| |
| int |
| dhd_os_detach_pktlog(dhd_pub_t *dhdp) |
| { |
| if (!dhdp || !dhdp->pktlog) { |
| DHD_PKT_LOG(("%s(): dhdp=%p pktlog=%p\n", |
| __FUNCTION__, dhdp, (dhdp ? dhdp->pktlog : NULL))); |
| return -EINVAL; |
| } |
| |
| dhd_pktlog_ring_deinit(dhdp, dhdp->pktlog->pktlog_ring); |
| dhd_pktlog_filter_deinit(dhdp->pktlog->pktlog_filter); |
| #ifdef DHD_COMPACT_PKT_LOG |
| dhd_cpkt_log_deinit_tt(dhdp); |
| #endif /* DHD_COMPACT_PKT_LOG */ |
| |
| DHD_INFO(("%s(): dhd_os_attach_pktlog detach\n", __FUNCTION__)); |
| |
| VMFREE(dhdp->osh, dhdp->pktlog, sizeof(dhd_pktlog_t)); |
| |
| return BCME_OK; |
| } |
| |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| int dhd_pktlog_is_enabled(dhd_pub_t *dhdp) |
| { |
| if (!dhdp || !dhdp->pktlog) { |
| DHD_ERROR(("%s(): dhdp=%p pktlog=%p\n", |
| __FUNCTION__, dhdp, (dhdp ? dhdp->pktlog : NULL))); |
| return BCME_ERROR; |
| } |
| |
| if (!dhdp->pktlog->pktlog_ring) { |
| DHD_ERROR(("%s(): pktlog_ring=%p\n", |
| __FUNCTION__, dhdp->pktlog->pktlog_ring)); |
| return BCME_ERROR; |
| } |
| |
| return OSL_ATOMIC_READ(dhdp->osh, &dhdp->pktlog->enable); |
| } |
| |
| void dhd_pktlog_resume(dhd_pub_t *dhdp) |
| { |
| struct dhd_pktlog_ring *pktlog_ring; |
| |
| if (!dhdp || !dhdp->pktlog) { |
| DHD_ERROR(("%s(): dhdp=%p pktlog=%p\n", |
| __FUNCTION__, dhdp, (dhdp ? dhdp->pktlog : NULL))); |
| return; |
| } |
| |
| if (!dhdp->pktlog->pktlog_ring) { |
| DHD_ERROR(("%s(): pktlog_ring=%p\n", |
| __FUNCTION__, dhdp->pktlog->pktlog_ring)); |
| return; |
| } |
| |
| pktlog_ring = dhdp->pktlog->pktlog_ring; |
| if (!OSL_ATOMIC_READ(dhdp->osh, &dhdp->pktlog->enable)) { |
| OSL_ATOMIC_SET(dhdp->osh, &dhdp->pktlog->enable, TRUE); |
| DHD_INFO(("pktlog ring is resumed. pktcount(%d)\n", |
| pktlog_ring->pktcount)); |
| } |
| } |
| |
| void dhd_pktlog_suspend(dhd_pub_t *dhdp) |
| { |
| struct dhd_pktlog_ring *pktlog_ring; |
| |
| if (!dhdp || !dhdp->pktlog) { |
| DHD_ERROR(("%s(): dhdp=%p pktlog=%p\n", |
| __FUNCTION__, dhdp, (dhdp ? dhdp->pktlog : NULL))); |
| return; |
| } |
| |
| if (!dhdp->pktlog->pktlog_ring) { |
| DHD_ERROR(("%s(): pktlog_ring=%p\n", |
| __FUNCTION__, dhdp->pktlog->pktlog_ring)); |
| return; |
| } |
| |
| pktlog_ring = dhdp->pktlog->pktlog_ring; |
| if (OSL_ATOMIC_READ(dhdp->osh, &dhdp->pktlog->enable)) { |
| OSL_ATOMIC_SET(dhdp->osh, &dhdp->pktlog->enable, FALSE); |
| DHD_INFO(("pktlog ring is suspended. pktcount(%d)\n", |
| pktlog_ring->pktcount)); |
| } |
| } |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| |
| dhd_pktlog_ring_t* |
| dhd_pktlog_ring_init(dhd_pub_t *dhdp, int size) |
| { |
| dhd_pktlog_ring_t *ring; |
| int i = 0; |
| |
| if (!dhdp) { |
| DHD_ERROR(("%s(): dhdp is NULL\n", __FUNCTION__)); |
| return NULL; |
| } |
| |
| ring = (dhd_pktlog_ring_t *)VMALLOCZ(dhdp->osh, sizeof(dhd_pktlog_ring_t)); |
| if (unlikely(!ring)) { |
| DHD_ERROR(("%s(): could not allocate memory for - " |
| "dhd_pktlog_ring_t\n", __FUNCTION__)); |
| goto fail; |
| } |
| |
| dll_init(&ring->ring_info_head); |
| dll_init(&ring->ring_info_free); |
| |
| ring->ring_info_mem = (dhd_pktlog_ring_info_t *)VMALLOCZ(dhdp->osh, |
| sizeof(dhd_pktlog_ring_info_t) * size); |
| if (unlikely(!ring->ring_info_mem)) { |
| DHD_ERROR(("%s(): could not allocate memory for - " |
| "dhd_pktlog_ring_info_t\n", __FUNCTION__)); |
| goto fail; |
| } |
| |
| /* initialize free ring_info linked list */ |
| for (i = 0; i < size; i++) { |
| dll_append(&ring->ring_info_free, (dll_t *)&ring->ring_info_mem[i].p_info); |
| } |
| |
| OSL_ATOMIC_SET(dhdp->osh, &ring->start, TRUE); |
| ring->pktlog_minmize = FALSE; |
| ring->pktlog_len = size; |
| ring->pktcount = 0; |
| ring->dhdp = dhdp; |
| ring->pktlog_ring_lock = osl_spin_lock_init(dhdp->osh); |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| OSL_ATOMIC_SET(dhdp->osh, &dhdp->pktlog->enable, TRUE); |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| |
| DHD_INFO(("%s(): pktlog ring init success\n", __FUNCTION__)); |
| |
| return ring; |
| fail: |
| if (ring) { |
| VMFREE(dhdp->osh, ring, sizeof(dhd_pktlog_ring_t)); |
| } |
| |
| return NULL; |
| } |
| |
| /* Maximum wait counts */ |
| #define DHD_PKTLOG_WAIT_MAXCOUNT 1000 |
| int |
| dhd_pktlog_ring_deinit(dhd_pub_t *dhdp, dhd_pktlog_ring_t *ring) |
| { |
| int ret = BCME_OK; |
| dhd_pktlog_ring_info_t *ring_info; |
| dll_t *item, *next_p; |
| int waitcounts = 0; |
| |
| if (!ring) { |
| DHD_ERROR(("%s(): ring is NULL\n", __FUNCTION__)); |
| return -EINVAL; |
| } |
| |
| if (!ring->dhdp) { |
| DHD_ERROR(("%s(): dhdp is NULL\n", __FUNCTION__)); |
| return -EINVAL; |
| } |
| |
| /* stop pkt log */ |
| OSL_ATOMIC_SET(dhdp->osh, &ring->start, FALSE); |
| |
| /* waiting TX/RX/TXS context is done, max timeout 1 second */ |
| while ((waitcounts++ < DHD_PKTLOG_WAIT_MAXCOUNT)) { |
| if (!OSL_ATOMIC_READ(dhdp->osh, &dhdp->pktlog->pktlog_status)) |
| break; |
| OSL_SLEEP(1); |
| } |
| |
| if (waitcounts >= DHD_PKTLOG_WAIT_MAXCOUNT) { |
| DHD_ERROR(("%s(): pktlog wait timeout pktlog_status : 0x%x \n", |
| __FUNCTION__, |
| OSL_ATOMIC_READ(dhdp->osh, &dhdp->pktlog->pktlog_status))); |
| ASSERT(0); |
| return -EINVAL; |
| } |
| |
| /* free ring_info->info.pkt */ |
| for (item = dll_head_p(&ring->ring_info_head); !dll_end(&ring->ring_info_head, item); |
| item = next_p) { |
| next_p = dll_next_p(item); |
| |
| ring_info = (dhd_pktlog_ring_info_t *)item; |
| |
| if (ring_info->info.pkt) { |
| PKTFREE(ring->dhdp->osh, ring_info->info.pkt, TRUE); |
| DHD_PKT_LOG(("%s(): pkt free pos %p\n", |
| __FUNCTION__, ring_info->info.pkt)); |
| } |
| } |
| |
| if (ring->ring_info_mem) { |
| VMFREE(ring->dhdp->osh, ring->ring_info_mem, |
| sizeof(dhd_pktlog_ring_info_t) * ring->pktlog_len); |
| } |
| |
| if (ring->pktlog_ring_lock) { |
| osl_spin_lock_deinit(ring->dhdp->osh, ring->pktlog_ring_lock); |
| } |
| |
| VMFREE(dhdp->osh, ring, sizeof(dhd_pktlog_ring_t)); |
| |
| DHD_INFO(("%s(): pktlog ring deinit\n", __FUNCTION__)); |
| |
| return ret; |
| } |
| |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| int |
| dhd_pktlog_ring_reinit(dhd_pub_t *dhdp) |
| { |
| dhd_pktlog_ring_info_t *ring_info; |
| dhd_pktlog_ring_t *pktlog_ring; |
| dll_t *item, *next_p; |
| int waitcounts = 0; |
| dhd_dbg_ring_t *ring; |
| |
| DHD_ERROR(("%s: ENTER\n", __FUNCTION__)); |
| |
| if (!dhdp) { |
| DHD_ERROR(("%s(): dhdp is NULL\n", __FUNCTION__)); |
| return -EINVAL; |
| } |
| pktlog_ring = dhdp->pktlog->pktlog_ring; |
| ring = &dhdp->dbg->dbg_rings[PACKET_LOG_RING_ID]; |
| |
| if (!ring) { |
| DHD_ERROR(("%s(): ring is NULL\n", __FUNCTION__)); |
| return -EINVAL; |
| } |
| |
| /* stop pkt log */ |
| OSL_ATOMIC_SET(dhdp->osh, &pktlog_ring->start, FALSE); |
| |
| /* waiting TX/RX/TXS context is done, max timeout 1 second */ |
| while ((waitcounts++ < DHD_PKTLOG_WAIT_MAXCOUNT)) { |
| if (!OSL_ATOMIC_READ(dhdp->osh, &dhdp->pktlog->pktlog_status)) |
| break; |
| OSL_SLEEP(1); |
| } |
| |
| if (waitcounts >= DHD_PKTLOG_WAIT_MAXCOUNT) { |
| DHD_ERROR(("%s(): pktlog wait timeout pktlog_status : 0x%x \n", |
| __FUNCTION__, |
| OSL_ATOMIC_READ(dhdp->osh, &dhdp->pktlog->pktlog_status))); |
| ASSERT(0); |
| return -EINVAL; |
| } |
| |
| /* free ring_info->info.pkt */ |
| for (item = dll_head_p(&pktlog_ring->ring_info_head); |
| !dll_end(&pktlog_ring->ring_info_head, item); |
| item = next_p) { |
| next_p = dll_next_p(item); |
| |
| ring_info = (dhd_pktlog_ring_info_t *)item; |
| |
| dll_delete((dll_t *)item); |
| if (ring_info->info.pkt) { |
| PKTFREE(pktlog_ring->dhdp->osh, ring_info->info.pkt, TRUE); |
| DHD_PKT_LOG(("%s(): pkt free pos %p\n", |
| __FUNCTION__, ring_info->info.pkt)); |
| } |
| dll_append(&pktlog_ring->ring_info_free, (dll_t *)item); |
| } |
| |
| /* reset stats and pktcount */ |
| pktlog_ring->pktcount = 0; |
| memset(&ring->stat, 0, sizeof(struct ring_statistics)); |
| /* start pkt log */ |
| OSL_ATOMIC_SET(dhdp->osh, &pktlog_ring->start, TRUE); |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| OSL_ATOMIC_SET(dhdp->osh, &dhdp->pktlog->enable, TRUE); |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| DHD_ERROR(("%s: EXIT\n", __FUNCTION__)); |
| |
| return BCME_OK; |
| } |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| |
| /* |
| * dhd_pktlog_ring_add_pkts : add filtered packets into pktlog ring |
| * pktid : incase of rx, pktid is not used (pass DHD_INVALID_PKID) |
| * direction : 1 - TX / 0 - RX / 2 - RX Wakeup Packet |
| */ |
| int |
| dhd_pktlog_ring_add_pkts(dhd_pub_t *dhdp, void *pkt, void *pktdata, uint32 pktid, uint32 direction) |
| { |
| dhd_pktlog_ring_info_t *pkts; |
| dhd_pktlog_ring_t *pktlog_ring; |
| dhd_pktlog_filter_t *pktlog_filter; |
| uint32 pktlog_case = 0; |
| unsigned long flags = 0; |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| struct timespec64 ts; |
| dhd_dbg_ring_t *ring; |
| ring = &dhdp->dbg->dbg_rings[PACKET_LOG_RING_ID]; |
| #else |
| u64 ts_nsec; |
| unsigned long rem_nsec; |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| |
| /* |
| * dhdp, dhdp->pktlog, dhd->pktlog_ring, pktlog_ring->start |
| * are validated from the DHD_PKTLOG_TX macro |
| */ |
| |
| pktlog_ring = dhdp->pktlog->pktlog_ring; |
| pktlog_filter = dhdp->pktlog->pktlog_filter; |
| |
| if (direction == PKT_TX) { |
| pktlog_case = PKTLOG_TXPKT_CASE; |
| } else if ((direction == PKT_RX) || (direction == PKT_WAKERX)) { |
| pktlog_case = PKTLOG_RXPKT_CASE; |
| } |
| |
| if (pktlog_filter && pktlog_filter->list_cnt == 0) { |
| /* There is no packet log list */ |
| return BCME_OK; |
| } |
| |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| if (ring->log_level == RING_LOG_LEVEL_EXCESSIVE) { |
| /* In excessive log level, pass through without pattern matching */ |
| } else |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| if ((direction != PKT_WAKERX) && |
| dhd_pktlog_filter_matched(pktlog_filter, pktdata, pktlog_case) |
| == FALSE) { |
| return BCME_OK; |
| } |
| |
| if (direction == PKT_TX && pktid == DHD_INVALID_PKTID) { |
| DHD_ERROR(("%s : Invalid PKTID \n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| /* get free ring_info and insert to ring_info_head */ |
| DHD_PKT_LOG_LOCK(pktlog_ring->pktlog_ring_lock, flags); |
| /* if free_list is empty, use the oldest ring_info */ |
| if (dll_empty(&pktlog_ring->ring_info_free)) { |
| pkts = (dhd_pktlog_ring_info_t *)dll_head_p(&pktlog_ring->ring_info_head); |
| dll_delete((dll_t *)pkts); |
| /* free the oldest packet */ |
| PKTFREE(pktlog_ring->dhdp->osh, pkts->info.pkt, TRUE); |
| pktlog_ring->pktcount--; |
| } else { |
| pkts = (dhd_pktlog_ring_info_t *)dll_tail_p(&pktlog_ring->ring_info_free); |
| dll_delete((dll_t *)pkts); |
| } |
| |
| /* Update packet information */ |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| ktime_get_real_ts64(&ts); |
| |
| pkts->info.driver_ts_sec = (uint32)ts.tv_sec; |
| pkts->info.driver_ts_usec = (uint32)(ts.tv_nsec/NSEC_PER_USEC); |
| #else |
| ts_nsec = local_clock(); |
| rem_nsec = do_div(ts_nsec, NSEC_PER_SEC); |
| |
| pkts->info.driver_ts_sec = (uint32)ts_nsec; |
| pkts->info.driver_ts_usec = (uint32)(rem_nsec/NSEC_PER_USEC); |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| |
| pkts->info.pkt = PKTDUP(dhdp->osh, pkt); |
| /* |
| * skb clone can be NULL, but pktlog feature assume it alway can be cloned |
| * The dummy pkt info will be added in the list to fit pktcount & list items |
| * and handled in the dhd_pktlog_dump_write() with ignoring info.pkt |
| */ |
| if (pkts->info.pkt == NULL) { |
| DHD_ERROR(("%s : skb clone returns NULL \n", __FUNCTION__)); |
| } |
| pkts->info.pkt_len = PKTLEN(dhdp->osh, pkt); |
| pkts->info.firmware_ts = 0U; |
| pkts->info.payload_type = FRAME_TYPE_ETHERNET_II; |
| pkts->info.direction = direction; |
| |
| pkts->info.tx_status_ts_sec = 0; |
| pkts->info.tx_status_ts_usec = 0; |
| |
| if (direction == PKT_TX) { |
| pkts->info.pkt_hash = __dhd_dbg_pkt_hash((uintptr_t)pkt, pktid); |
| pkts->tx_fate = TX_PKT_FATE_DRV_QUEUED; |
| } else if (direction == PKT_RX) { |
| pkts->info.pkt_hash = 0U; |
| pkts->rx_fate = RX_PKT_FATE_SUCCESS; |
| } else if (direction == PKT_WAKERX) { |
| pkts->info.pkt_hash = 0U; |
| pkts->rx_fate = RX_PKT_FATE_WAKE_PKT; |
| } |
| |
| DHD_PKT_LOG(("%s(): pkt hash %d\n", __FUNCTION__, pkts->info.pkt_hash)); |
| DHD_PKT_LOG(("%s(): sec %d usec %d\n", __FUNCTION__, |
| pkts->info.driver_ts_sec, pkts->info.driver_ts_usec)); |
| |
| /* insert tx_pkts to the pktlog_ring->ring_info_head */ |
| dll_append(&pktlog_ring->ring_info_head, (dll_t *)pkts); |
| pktlog_ring->pktcount++; |
| DHD_PKT_LOG_UNLOCK(pktlog_ring->pktlog_ring_lock, flags); |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| dhd_dbg_update_to_ring(dhdp, pktlog_ring->dbg_ring, |
| dhd_pktlog_get_item_length(pkts)); |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| |
| DHD_PKT_LOG(("%s(): pktcount=%d\n", __FUNCTION__, pktlog_ring->pktcount)); |
| return BCME_OK; |
| } |
| |
| int |
| dhd_pktlog_ring_tx_status(dhd_pub_t *dhdp, void *pkt, void *pktdata, uint32 pktid, |
| uint16 status) |
| { |
| dhd_pktlog_ring_info_t *tx_pkt; |
| wifi_tx_packet_fate pkt_fate; |
| uint32 pkt_hash, temp_hash; |
| dhd_pktlog_ring_t *pktlog_ring; |
| dhd_pktlog_filter_t *pktlog_filter; |
| dll_t *item_p, *next_p; |
| unsigned long flags = 0; |
| u64 ts_nsec; |
| unsigned long rem_nsec; |
| #ifdef BDC |
| struct bdc_header *h; |
| BCM_REFERENCE(h); |
| #endif /* BDC */ |
| /* |
| * dhdp, dhdp->pktlog, dhd->pktlog_ring, pktlog_ring->start |
| * are validated from the DHD_PKTLOG_TXS macro |
| */ |
| |
| pktlog_ring = dhdp->pktlog->pktlog_ring; |
| pktlog_filter = dhdp->pktlog->pktlog_filter; |
| |
| if (dhd_pktlog_filter_matched(pktlog_filter, pktdata, |
| PKTLOG_TXSTATUS_CASE) == FALSE) { |
| return BCME_OK; |
| } |
| |
| pkt_hash = __dhd_dbg_pkt_hash((uintptr_t)pkt, pktid); |
| pkt_fate = __dhd_dbg_map_tx_status_to_pkt_fate(status); |
| |
| ts_nsec = local_clock(); |
| rem_nsec = do_div(ts_nsec, NSEC_PER_SEC); |
| |
| /* find the sent tx packet and adding pkt_fate info */ |
| DHD_PKT_LOG_LOCK(pktlog_ring->pktlog_ring_lock, flags); |
| /* Inverse traverse from the last packets */ |
| for (item_p = dll_tail_p(&pktlog_ring->ring_info_head); |
| !dll_end(&pktlog_ring->ring_info_head, item_p); |
| item_p = next_p) |
| { |
| if (dll_empty(item_p)) { |
| break; |
| } |
| next_p = dll_prev_p(item_p); |
| tx_pkt = (dhd_pktlog_ring_info_t *)item_p; |
| temp_hash = tx_pkt->info.pkt_hash; |
| if (temp_hash == pkt_hash) { |
| tx_pkt->tx_fate = pkt_fate; |
| #ifdef BDC |
| h = (struct bdc_header *)PKTDATA(dhdp->osh, tx_pkt->info.pkt); |
| PKTPULL(dhdp->osh, tx_pkt->info.pkt, BDC_HEADER_LEN); |
| PKTPULL(dhdp->osh, tx_pkt->info.pkt, (h->dataOffset << DHD_WORD_TO_LEN_SHIFT)); |
| #endif /* BDC */ |
| tx_pkt->info.tx_status_ts_sec = (uint32)ts_nsec; |
| tx_pkt->info.tx_status_ts_usec = (uint32)(rem_nsec/NSEC_PER_USEC); |
| DHD_PKT_LOG(("%s(): Found pkt hash in prev pos\n", __FUNCTION__)); |
| break; |
| } |
| } |
| DHD_PKT_LOG_UNLOCK(pktlog_ring->pktlog_ring_lock, flags); |
| return BCME_OK; |
| } |
| |
| dhd_pktlog_filter_t* |
| dhd_pktlog_filter_init(int size) |
| { |
| int i; |
| gfp_t kflags; |
| uint32 alloc_len; |
| dhd_pktlog_filter_t *filter; |
| dhd_pktlog_filter_info_t *filter_info = NULL; |
| |
| kflags = in_atomic() ? GFP_ATOMIC : GFP_KERNEL; |
| |
| /* allocate and initialze pktmon filter */ |
| alloc_len = sizeof(dhd_pktlog_filter_t); |
| filter = (dhd_pktlog_filter_t *)kzalloc(alloc_len, kflags); |
| if (unlikely(!filter)) { |
| DHD_ERROR(("%s(): could not allocate memory for - " |
| "dhd_pktlog_filter_t\n", __FUNCTION__)); |
| goto fail; |
| } |
| |
| alloc_len = (sizeof(dhd_pktlog_filter_info_t) * size); |
| filter_info = (dhd_pktlog_filter_info_t *)kzalloc(alloc_len, kflags); |
| if (unlikely(!filter_info)) { |
| DHD_ERROR(("%s(): could not allocate memory for - " |
| "dhd_pktlog_filter_info_t\n", __FUNCTION__)); |
| goto fail; |
| } |
| |
| filter->info = filter_info; |
| filter->list_cnt = 0; |
| |
| for (i = 0; i < MAX_DHD_PKTLOG_FILTER_LEN; i++) { |
| filter->info[i].id = 0; |
| } |
| |
| filter->enable = PKTLOG_TXPKT_CASE | PKTLOG_TXSTATUS_CASE | PKTLOG_RXPKT_CASE; |
| |
| DHD_INFO(("%s(): pktlog filter init success\n", __FUNCTION__)); |
| |
| return filter; |
| fail: |
| if (filter) { |
| kfree(filter); |
| } |
| |
| return NULL; |
| } |
| |
| int |
| dhd_pktlog_filter_deinit(dhd_pktlog_filter_t *filter) |
| { |
| int ret = BCME_OK; |
| |
| if (!filter) { |
| DHD_ERROR(("%s(): filter is NULL\n", __FUNCTION__)); |
| return -EINVAL; |
| } |
| |
| if (filter->info) { |
| kfree(filter->info); |
| } |
| kfree(filter); |
| |
| DHD_INFO(("%s(): pktlog filter deinit\n", __FUNCTION__)); |
| |
| return ret; |
| } |
| |
| bool |
| dhd_pktlog_filter_existed(dhd_pktlog_filter_t *filter, char *arg, uint32 *id) |
| { |
| char filter_pattern[MAX_FILTER_PATTERN_LEN]; |
| char *p; |
| int i, j; |
| int nchar; |
| int len; |
| |
| if (!filter || !arg) { |
| DHD_ERROR(("%s(): filter=%p arg=%p\n", __FUNCTION__, filter, arg)); |
| return TRUE; |
| } |
| |
| for (i = 0; i < filter->list_cnt; i++) { |
| p = filter_pattern; |
| len = sizeof(filter_pattern); |
| |
| nchar = snprintf(p, len, "%d ", filter->info[i].offset); |
| p += nchar; |
| len -= nchar; |
| |
| nchar = snprintf(p, len, "0x"); |
| p += nchar; |
| len -= nchar; |
| |
| for (j = 0; j < filter->info[i].size_bytes; j++) { |
| nchar = snprintf(p, len, "%02x", filter->info[i].mask[j]); |
| p += nchar; |
| len -= nchar; |
| } |
| |
| nchar = snprintf(p, len, " 0x"); |
| p += nchar; |
| len -= nchar; |
| |
| for (j = 0; j < filter->info[i].size_bytes; j++) { |
| nchar = snprintf(p, len, "%02x", filter->info[i].pattern[j]); |
| p += nchar; |
| len -= nchar; |
| } |
| |
| if (strlen(arg) < strlen(filter_pattern)) { |
| continue; |
| } |
| |
| DHD_PKT_LOG(("%s(): Pattern %s\n", __FUNCTION__, filter_pattern)); |
| |
| if (strncmp(filter_pattern, arg, strlen(filter_pattern)) == 0) { |
| *id = filter->info[i].id; |
| DHD_ERROR(("%s(): This pattern is existed\n", __FUNCTION__)); |
| DHD_ERROR(("%s(): arg %s\n", __FUNCTION__, arg)); |
| return TRUE; |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| int |
| dhd_pktlog_filter_add(dhd_pktlog_filter_t *filter, char *arg) |
| { |
| int32 mask_size, pattern_size; |
| char *offset, *bitmask, *pattern; |
| uint32 id = 0; |
| |
| if (!filter || !arg) { |
| DHD_ERROR(("%s(): pktlog_filter =%p arg =%p\n", __FUNCTION__, filter, arg)); |
| return BCME_ERROR; |
| } |
| |
| DHD_PKT_LOG(("%s(): arg %s\n", __FUNCTION__, arg)); |
| |
| if (dhd_pktlog_filter_existed(filter, arg, &id) == TRUE) { |
| DHD_PKT_LOG(("%s(): This pattern id %d is existed\n", __FUNCTION__, id)); |
| return BCME_OK; |
| } |
| |
| if (filter->list_cnt >= MAX_DHD_PKTLOG_FILTER_LEN) { |
| DHD_ERROR(("%s(): pktlog filter full\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| if ((offset = bcmstrtok(&arg, " ", 0)) == NULL) { |
| DHD_ERROR(("%s(): offset not found\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| if ((bitmask = bcmstrtok(&arg, " ", 0)) == NULL) { |
| DHD_ERROR(("%s(): bitmask not found\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| if ((pattern = bcmstrtok(&arg, " ", 0)) == NULL) { |
| DHD_ERROR(("%s(): pattern not found\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| /* parse filter bitmask */ |
| mask_size = pattern_atoh_len(bitmask, |
| (char *) &filter->info[filter->list_cnt].mask[0], |
| MAX_MASK_PATTERN_FILTER_LEN); |
| if (mask_size == -1) { |
| DHD_ERROR(("Rejecting: %s\n", bitmask)); |
| return BCME_ERROR; |
| } |
| |
| /* parse filter pattern */ |
| pattern_size = pattern_atoh_len(pattern, |
| (char *) &filter->info[filter->list_cnt].pattern[0], |
| MAX_MASK_PATTERN_FILTER_LEN); |
| if (pattern_size == -1) { |
| DHD_ERROR(("Rejecting: %s\n", pattern)); |
| return BCME_ERROR; |
| } |
| |
| prhex("mask", (char *)&filter->info[filter->list_cnt].mask[0], |
| mask_size); |
| prhex("pattern", (char *)&filter->info[filter->list_cnt].pattern[0], |
| pattern_size); |
| |
| if (mask_size != pattern_size) { |
| DHD_ERROR(("%s(): Mask and pattern not the same size\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| filter->info[filter->list_cnt].offset = strtoul(offset, NULL, 0); |
| filter->info[filter->list_cnt].size_bytes = mask_size; |
| filter->info[filter->list_cnt].id = filter->list_cnt + 1; |
| filter->info[filter->list_cnt].enable = TRUE; |
| |
| filter->list_cnt++; |
| |
| return BCME_OK; |
| } |
| |
| int |
| dhd_pktlog_filter_del(dhd_pktlog_filter_t *filter, char *arg) |
| { |
| uint32 id = 0; |
| |
| if (!filter || !arg) { |
| DHD_ERROR(("%s(): pktlog_filter =%p arg =%p\n", __FUNCTION__, filter, arg)); |
| return BCME_ERROR; |
| } |
| |
| DHD_PKT_LOG(("%s(): arg %s\n", __FUNCTION__, arg)); |
| |
| if (dhd_pktlog_filter_existed(filter, arg, &id) != TRUE) { |
| DHD_PKT_LOG(("%s(): This pattern id %d doesn't existed\n", __FUNCTION__, id)); |
| return BCME_OK; |
| } |
| |
| dhd_pktlog_filter_pull_forward(filter, id, filter->list_cnt); |
| |
| filter->list_cnt--; |
| |
| return BCME_OK; |
| } |
| |
| int |
| dhd_pktlog_filter_enable(dhd_pktlog_filter_t *filter, uint32 pktmon_case, uint32 enable) |
| { |
| if (!filter) { |
| DHD_ERROR(("%s(): filter is NULL\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| DHD_PKT_LOG(("%s(): pktlog_case %d enable %d\n", __FUNCTION__, pktmon_case, enable)); |
| |
| if (enable) { |
| filter->enable |= pktmon_case; |
| } else { |
| filter->enable &= ~pktmon_case; |
| } |
| |
| return BCME_OK; |
| } |
| |
| int |
| dhd_pktlog_filter_pattern_enable(dhd_pktlog_filter_t *filter, char *arg, uint32 enable) |
| { |
| uint32 id = 0; |
| |
| if (!filter || !arg) { |
| DHD_ERROR(("%s(): pktlog_filter =%p arg =%p\n", __FUNCTION__, filter, arg)); |
| return BCME_ERROR; |
| } |
| |
| if (dhd_pktlog_filter_existed(filter, arg, &id) == TRUE) { |
| if (id > 0) { |
| filter->info[id-1].enable = enable; |
| DHD_ERROR(("%s(): This pattern id %d is %s\n", |
| __FUNCTION__, id, (enable ? "enabled" : "disabled"))); |
| } |
| } else { |
| DHD_ERROR(("%s(): This pattern is not existed\n", __FUNCTION__)); |
| DHD_ERROR(("%s(): arg %s\n", __FUNCTION__, arg)); |
| } |
| |
| return BCME_OK; |
| } |
| |
| int |
| dhd_pktlog_filter_info(dhd_pktlog_filter_t *filter) |
| { |
| char filter_pattern[MAX_FILTER_PATTERN_LEN]; |
| char *p; |
| int i, j; |
| int nchar; |
| int len; |
| |
| if (!filter) { |
| DHD_ERROR(("%s(): pktlog_filter is NULL\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| DHD_ERROR(("---- PKTLOG FILTER INFO ----\n\n")); |
| |
| DHD_ERROR(("Filter list cnt %d Filter is %s\n", |
| filter->list_cnt, (filter->enable ? "enabled" : "disabled"))); |
| |
| for (i = 0; i < filter->list_cnt; i++) { |
| p = filter_pattern; |
| len = sizeof(filter_pattern); |
| |
| nchar = snprintf(p, len, "%d ", filter->info[i].offset); |
| p += nchar; |
| len -= nchar; |
| |
| nchar = snprintf(p, len, "0x"); |
| p += nchar; |
| len -= nchar; |
| |
| for (j = 0; j < filter->info[i].size_bytes; j++) { |
| nchar = snprintf(p, len, "%02x", filter->info[i].mask[j]); |
| p += nchar; |
| len -= nchar; |
| } |
| |
| nchar = snprintf(p, len, " 0x"); |
| p += nchar; |
| len -= nchar; |
| |
| for (j = 0; j < filter->info[i].size_bytes; j++) { |
| nchar = snprintf(p, len, "%02x", filter->info[i].pattern[j]); |
| p += nchar; |
| len -= nchar; |
| } |
| |
| DHD_ERROR(("ID:%d is %s\n", |
| filter->info[i].id, (filter->info[i].enable ? "enabled" : "disabled"))); |
| DHD_ERROR(("Pattern %s\n", filter_pattern)); |
| } |
| |
| DHD_ERROR(("---- PKTLOG FILTER END ----\n")); |
| |
| return BCME_OK; |
| } |
| bool |
| dhd_pktlog_filter_matched(dhd_pktlog_filter_t *filter, char *data, uint32 pktlog_case) |
| { |
| uint16 szbts; /* pattern size */ |
| uint16 offset; /* pattern offset */ |
| int i, j; |
| uint8 *mask = NULL; /* bitmask */ |
| uint8 *pattern = NULL; |
| uint8 *pkt_offset = NULL; /* packet offset */ |
| bool matched; |
| |
| if (!filter || !data) { |
| DHD_PKT_LOG(("%s(): filter=%p data=%p\n", |
| __FUNCTION__, filter, data)); |
| return TRUE; |
| } |
| |
| if (!(pktlog_case & filter->enable)) { |
| DHD_PKT_LOG(("%s(): pktlog_case %d return TRUE filter is disabled\n", |
| __FUNCTION__, pktlog_case)); |
| return TRUE; |
| } |
| |
| for (i = 0; i < filter->list_cnt; i++) { |
| if (&filter->info[i] && filter->info[i].id && filter->info[i].enable) { |
| szbts = filter->info[i].size_bytes; |
| offset = filter->info[i].offset; |
| mask = &filter->info[i].mask[0]; |
| pkt_offset = &data[offset]; |
| pattern = &filter->info[i].pattern[0]; |
| |
| matched = TRUE; |
| for (j = 0; j < szbts; j++) { |
| if ((mask[j] & pkt_offset[j]) != pattern[j]) { |
| matched = FALSE; |
| break; |
| } |
| } |
| |
| if (matched) { |
| DHD_PKT_LOG(("%s(): pktlog_filter return TRUE id %d\n", |
| __FUNCTION__, filter->info[i].id)); |
| return TRUE; |
| } |
| } else { |
| DHD_PKT_LOG(("%s(): filter ino is null %p\n", |
| __FUNCTION__, &filter->info[i])); |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| /* Ethernet Type MAC Header 12 bytes + Frame payload 10 bytes */ |
| #define PKTLOG_MINIMIZE_REPORT_LEN 22 |
| |
| #ifndef DHD_PKT_LOGGING_DBGRING |
| static char pktlog_minmize_mask_table[] = { |
| 0xff, 0x00, 0x00, 0x00, 0xff, 0x0f, /* Ethernet Type MAC Header - Destination MAC Address */ |
| 0xff, 0x00, 0x00, 0x00, 0xff, 0x0f, /* Ethernet Type MAC Header - Source MAC Address */ |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* Ethernet Type MAC Header - Ether Type - 2 bytes */ |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* Frame payload */ |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, /* UDP port number offset - bytes as 0xff */ |
| 0xff, 0xff, |
| }; |
| |
| static inline void |
| dhd_pktlog_minimize_report(char *pkt, uint32 frame_len, |
| void *file, const void *user_buf, void *pos) |
| { |
| int i; |
| int ret = 0; |
| int table_len; |
| int report_len; |
| char *p_table; |
| char *mem_buf = NULL; |
| |
| table_len = sizeof(pktlog_minmize_mask_table); |
| report_len = table_len; |
| p_table = &pktlog_minmize_mask_table[0]; |
| |
| if (frame_len < PKTLOG_MINIMIZE_REPORT_LEN) { |
| DHD_ERROR(("%s : frame_len is samller than min\n", __FUNCTION__)); |
| return; |
| } |
| |
| mem_buf = vmalloc(frame_len); |
| if (!mem_buf) { |
| DHD_ERROR(("%s : failed to alloc membuf\n", __FUNCTION__)); |
| return; |
| } |
| |
| bzero(mem_buf, frame_len); |
| |
| if (frame_len < table_len) { |
| report_len = PKTLOG_MINIMIZE_REPORT_LEN; |
| } |
| |
| for (i = 0; i < report_len; i++) { |
| mem_buf[i] = pkt[i] & p_table[i]; |
| } |
| |
| ret = dhd_export_debug_data(mem_buf, |
| file, user_buf, frame_len, pos); |
| if (ret < 0) { |
| DHD_ERROR(("%s : Write minimize report\n", __FUNCTION__)); |
| } |
| vfree(mem_buf); |
| } |
| #endif /* !DHD_PKT_LOGGING_DBGRING */ |
| |
| dhd_pktlog_ring_t* |
| dhd_pktlog_ring_change_size(dhd_pktlog_ring_t *ringbuf, int size) |
| { |
| uint32 alloc_len; |
| uint32 pktlog_minmize; |
| dhd_pktlog_ring_t *pktlog_ring = NULL; |
| dhd_pub_t *dhdp; |
| |
| if (!ringbuf) { |
| DHD_ERROR(("%s(): ringbuf is NULL\n", __FUNCTION__)); |
| return NULL; |
| } |
| |
| alloc_len = size; |
| if (alloc_len < MIN_PKTLOG_LEN) { |
| alloc_len = MIN_PKTLOG_LEN; |
| } |
| if (alloc_len > MAX_PKTLOG_LEN) { |
| alloc_len = MAX_PKTLOG_LEN; |
| } |
| DHD_ERROR(("ring size requested: %d alloc: %d\n", size, alloc_len)); |
| |
| /* backup variable */ |
| pktlog_minmize = ringbuf->pktlog_minmize; |
| dhdp = ringbuf->dhdp; |
| |
| /* free ring_info */ |
| dhd_pktlog_ring_deinit(dhdp, ringbuf); |
| |
| /* alloc ring_info */ |
| pktlog_ring = dhd_pktlog_ring_init(dhdp, alloc_len); |
| |
| /* restore variable */ |
| if (pktlog_ring) { |
| OSL_ATOMIC_SET(dhdp->osh, &pktlog_ring->start, TRUE); |
| pktlog_ring->pktlog_minmize = pktlog_minmize; |
| } |
| |
| return pktlog_ring; |
| } |
| |
| void |
| dhd_pktlog_filter_pull_forward(dhd_pktlog_filter_t *filter, uint32 del_filter_id, uint32 list_cnt) |
| { |
| int ret = 0; |
| int pos = 0; |
| int move_list_cnt = 0; |
| int move_bytes = 0; |
| |
| if ((del_filter_id > list_cnt) || |
| (list_cnt > MAX_DHD_PKTLOG_FILTER_LEN)) { |
| DHD_ERROR(("Wrong id %d cnt %d tried to remove\n", del_filter_id, list_cnt)); |
| return; |
| } |
| |
| move_list_cnt = list_cnt - del_filter_id; |
| |
| pos = del_filter_id -1; |
| move_bytes = sizeof(dhd_pktlog_filter_info_t) * move_list_cnt; |
| if (move_list_cnt) { |
| ret = memmove_s(&filter->info[pos], move_bytes + sizeof(dhd_pktlog_filter_info_t), |
| &filter->info[pos+1], move_bytes); |
| if (ret) { |
| DHD_ERROR(("filter moving failed\n")); |
| return; |
| } |
| for (; pos < list_cnt -1; pos++) { |
| filter->info[pos].id -= 1; |
| } |
| } |
| bzero(&filter->info[list_cnt-1], sizeof(dhd_pktlog_filter_info_t)); |
| } |
| |
| void dhd_pktlog_get_filename(dhd_pub_t *dhdp, char *dump_path, int len) |
| { |
| /* Init file name */ |
| bzero(dump_path, len); |
| clear_debug_dump_time(dhdp->debug_dump_time_pktlog_str); |
| get_debug_dump_time(dhdp->debug_dump_time_pktlog_str); |
| |
| if (dhdp->memdump_type == DUMP_TYPE_BY_SYSDUMP) { |
| if (dhdp->debug_dump_subcmd == CMD_UNWANTED) { |
| snprintf(dump_path, len, "%s", |
| DHD_PKTLOG_DUMP_PATH DHD_PKTLOG_DUMP_TYPE |
| DHD_DUMP_SUBSTR_UNWANTED); |
| } else if (dhdp->debug_dump_subcmd == CMD_DISCONNECTED) { |
| snprintf(dump_path, len, "%s", |
| DHD_PKTLOG_DUMP_PATH DHD_PKTLOG_DUMP_TYPE |
| DHD_DUMP_SUBSTR_DISCONNECTED); |
| } else { |
| snprintf(dump_path, len, "%s", |
| DHD_PKTLOG_DUMP_PATH DHD_PKTLOG_DUMP_TYPE); |
| } |
| } else { |
| if (dhdp->pktlog_debug) { |
| snprintf(dump_path, len, "%s", |
| DHD_PKTLOG_DUMP_PATH DHD_PKTLOG_DEBUG_DUMP_TYPE); |
| } else { |
| snprintf(dump_path, len, "%s", |
| DHD_PKTLOG_DUMP_PATH DHD_PKTLOG_DUMP_TYPE); |
| } |
| |
| } |
| |
| snprintf(dump_path, len, "%s_%s.pcap", dump_path, |
| dhdp->debug_dump_time_pktlog_str); |
| DHD_ERROR(("%s: pktlog path = %s%s\n", __FUNCTION__, dump_path, FILE_NAME_HAL_TAG)); |
| clear_debug_dump_time(dhdp->debug_dump_time_pktlog_str); |
| } |
| |
| uint32 |
| dhd_pktlog_get_item_length(dhd_pktlog_ring_info_t *report_ptr) |
| { |
| uint32 len = 0; |
| char buf[DHD_PKTLOG_FATE_INFO_STR_LEN]; |
| int bytes_user_data = 0; |
| uint32 write_frame_len; |
| uint32 frame_len; |
| |
| len += (uint32)sizeof(report_ptr->info.driver_ts_sec); |
| len += (uint32)sizeof(report_ptr->info.driver_ts_usec); |
| |
| if (report_ptr->info.payload_type == FRAME_TYPE_ETHERNET_II) { |
| frame_len = (uint32)min(report_ptr->info.pkt_len, (size_t)MAX_FRAME_LEN_ETHERNET); |
| } else { |
| frame_len = (uint32)min(report_ptr->info.pkt_len, (size_t)MAX_FRAME_LEN_80211_MGMT); |
| } |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| frame_len = (uint32)min(frame_len, DHD_PKT_LOGGING_DBGRING_MAX_SIZE); |
| bytes_user_data = snprintf(buf, sizeof(buf), "%s:%s:%02d\n", |
| DHD_PKTLOG_FATE_INFO_FORMAT, |
| (report_ptr->tx_fate ? "Failure" : "Succeed"), report_ptr->tx_fate); |
| #else |
| bytes_user_data = snprintf(buf, sizeof(buf), "%s:%s:%02d:%s:%d.%d s\n", |
| DHD_PKTLOG_FATE_INFO_FORMAT, |
| (report_ptr->tx_fate ? "Failure" : "Succeed"), report_ptr->tx_fate, |
| (report_ptr->info.direction == PKT_TX) ? "TX" : "RX", |
| report_ptr->info.tx_status_ts_sec, report_ptr->info.tx_status_ts_usec); |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| write_frame_len = frame_len + bytes_user_data; |
| |
| /* pcap pkt head has incl_len and orig_len */ |
| len += (uint32)sizeof(write_frame_len); |
| len += (uint32)sizeof(write_frame_len); |
| len += write_frame_len; |
| |
| return len; |
| } |
| |
| int |
| dhd_pktlog_get_dump_length(dhd_pub_t *dhdp) |
| { |
| dhd_pktlog_ring_info_t *report_ptr; |
| dhd_pktlog_ring_t *pktlog_ring; |
| uint32 len = 0; |
| dll_t *item_p, *next_p; |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| dhd_dbg_ring_t *ring; |
| ring = &dhdp->dbg->dbg_rings[PACKET_LOG_RING_ID]; |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| |
| if (!dhdp || !dhdp->pktlog) { |
| DHD_PKT_LOG(("%s(): dhdp=%p pktlog=%p\n", |
| __FUNCTION__, dhdp, (dhdp ? dhdp->pktlog : NULL))); |
| return -EINVAL; |
| } |
| |
| if (!dhdp->pktlog->pktlog_ring) { |
| DHD_PKT_LOG(("%s(): pktlog_ring =%p\n", |
| __FUNCTION__, dhdp->pktlog->pktlog_ring)); |
| return -EINVAL; |
| } |
| |
| pktlog_ring = dhdp->pktlog->pktlog_ring; |
| OSL_ATOMIC_SET(dhdp->osh, &pktlog_ring->start, FALSE); |
| #ifndef DHD_PKT_LOGGING_DBGRING |
| len = sizeof(dhd_pktlog_pcap_hdr_t); |
| #endif /* !DHD_PKT_LOGGING_DBGRING */ |
| for (item_p = dll_head_p(&pktlog_ring->ring_info_head); |
| !dll_end(&pktlog_ring->ring_info_head, item_p); |
| item_p = next_p) { |
| next_p = dll_next_p(item_p); |
| report_ptr = (dhd_pktlog_ring_info_t *)item_p; |
| len += dhd_pktlog_get_item_length(report_ptr); |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| if (len >= ring->threshold) { |
| break; |
| } |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| } |
| OSL_ATOMIC_SET(dhdp->osh, &pktlog_ring->start, TRUE); |
| DHD_PKT_LOG(("calcuated pkt log dump len:%d\n", len)); |
| |
| return len; |
| } |
| |
| int |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| dhd_pktlog_dump_write(dhd_pub_t *dhdp, void *file, const void *user_buf, |
| uint32 *written_bytes) |
| #else |
| dhd_pktlog_dump_write(dhd_pub_t *dhdp, void *file, const void *user_buf, uint32 size) |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| { |
| dhd_pktlog_ring_info_t *report_ptr; |
| dhd_pktlog_ring_t *pktlog_ring; |
| char buf[DHD_PKTLOG_FATE_INFO_STR_LEN]; |
| #ifndef DHD_PKT_LOGGING_DBGRING |
| dhd_pktlog_pcap_hdr_t pcap_h; |
| loff_t pos = 0; |
| #else |
| uint32 size = 0; |
| unsigned long flags = 0; |
| #endif /* !DHD_PKT_LOGGING_DBGRING */ |
| uint32 write_frame_len; |
| uint32 frame_len; |
| ulong len = 0; |
| int bytes_user_data = 0; |
| int ret = BCME_OK; |
| dll_t *item_p, *next_p; |
| |
| if (!dhdp || !dhdp->pktlog) { |
| DHD_PKT_LOG(("%s(): dhdp=%p pktlog=%p\n", |
| __FUNCTION__, dhdp, (dhdp ? dhdp->pktlog : NULL))); |
| return -EINVAL; |
| } |
| |
| if (!dhdp->pktlog->pktlog_ring) { |
| DHD_PKT_LOG(("%s(): pktlog_ring =%p\n", |
| __FUNCTION__, dhdp->pktlog->pktlog_ring)); |
| return -EINVAL; |
| } |
| |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| if (user_buf) { |
| size = *written_bytes; |
| } else if (file) { |
| /* With DHD_PKT_LOGGING_DBGRING, this path is not allowed. */ |
| return BCME_UNSUPPORTED; |
| } |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| |
| if (file && !user_buf && (size == 0)) { |
| DHD_ERROR(("Local file pktlog dump requested\n")); |
| } else if (!file && user_buf && (size > 0)) { |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| DHD_INFO(("HAL file pktlog dump %d bytes requested\n", size)); |
| #else |
| DHD_ERROR(("HAL file pktlog dump %d bytes requested\n", size)); |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| } else { |
| DHD_ERROR(("Wrong type pktlog dump requested\n")); |
| return -EINVAL; |
| } |
| |
| pktlog_ring = dhdp->pktlog->pktlog_ring; |
| OSL_ATOMIC_SET(dhdp->osh, &pktlog_ring->start, FALSE); |
| #ifndef DHD_PKT_LOGGING_DBGRING |
| pcap_h.magic_number = PKTLOG_PCAP_MAGIC_NUM; |
| pcap_h.version_major = PKTLOG_PCAP_MAJOR_VER; |
| pcap_h.version_minor = PKTLOG_PCAP_MINOR_VER; |
| pcap_h.thiszone = 0x0; |
| pcap_h.sigfigs = 0x0; |
| pcap_h.snaplen = PKTLOG_PCAP_SNAP_LEN; |
| pcap_h.network = PKTLOG_PCAP_NETWORK_TYPE; |
| |
| ret = dhd_export_debug_data((char *)&pcap_h, file, user_buf, sizeof(pcap_h), &pos); |
| len = sizeof(pcap_h); |
| #endif /* !DHD_PKT_LOGGING_DBGRING */ |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| DHD_PKT_LOG_LOCK(dhd_os_get_pktlog_lock(dhdp), flags); |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| for (item_p = dll_head_p(&pktlog_ring->ring_info_head); |
| !dll_end(&pktlog_ring->ring_info_head, item_p); |
| item_p = next_p) { |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| uint32 captured_frame_len; |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| |
| next_p = dll_next_p(item_p); |
| report_ptr = (dhd_pktlog_ring_info_t *)item_p; |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| if (report_ptr->info.direction == PKT_TX && |
| report_ptr->tx_fate == TX_PKT_FATE_DRV_QUEUED) { |
| /* Not updated txfate yet, postpone some time. */ |
| report_ptr->tx_fate |= TX_PKT_FATE_DRV_WAIT_UPDATE; |
| continue; |
| } |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| if ((file == NULL) && |
| (len + dhd_pktlog_get_item_length(report_ptr) > size)) { |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| DHD_INFO(("overflowed pkt logs are dropped\n")); |
| #else |
| DHD_ERROR(("overflowed pkt logs are dropped\n")); |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| break; |
| } |
| |
| if (report_ptr->info.pkt == NULL) { |
| DHD_ERROR(("%s : pkt isn't located skip it\n", __FUNCTION__)); |
| continue; |
| } |
| |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| ret = memcpy_s((void*)(user_buf + len), size - len, |
| (char*)&report_ptr->info.driver_ts_sec, |
| sizeof(report_ptr->info.driver_ts_sec)); |
| len += sizeof(report_ptr->info.driver_ts_sec); |
| |
| ret = memcpy_s((void*)(user_buf + len), size - len, |
| (char*)&report_ptr->info.driver_ts_usec, |
| sizeof(report_ptr->info.driver_ts_usec)); |
| len += sizeof(report_ptr->info.driver_ts_usec); |
| |
| if (report_ptr->info.payload_type == FRAME_TYPE_ETHERNET_II) { |
| frame_len = (uint32)min(report_ptr->info.pkt_len, |
| (size_t)MAX_FRAME_LEN_ETHERNET); |
| } else { |
| frame_len = (uint32)min(report_ptr->info.pkt_len, |
| (size_t)MAX_FRAME_LEN_80211_MGMT); |
| } |
| |
| bytes_user_data = snprintf(buf, sizeof(buf), "%s:%s:%02d\n", |
| DHD_PKTLOG_FATE_INFO_FORMAT, |
| (report_ptr->tx_fate ? "Failure" : "Succeed"), |
| (report_ptr->tx_fate & !(TX_PKT_FATE_DRV_WAIT_UPDATE))); |
| write_frame_len = frame_len + bytes_user_data; |
| frame_len = (uint32)min(frame_len, DHD_PKT_LOGGING_DBGRING_MAX_SIZE); |
| captured_frame_len = frame_len + bytes_user_data; |
| |
| /* pcap pkt head has incl_len and orig_len */ |
| ret = memcpy_s((void*)(user_buf + len), size - len, |
| (char*)&captured_frame_len, sizeof(captured_frame_len)); |
| len += sizeof(captured_frame_len); |
| |
| ret = memcpy_s((void*)(user_buf + len), size - len, |
| (char*)&write_frame_len, sizeof(write_frame_len)); |
| len += sizeof(write_frame_len); |
| |
| ret = memcpy_s((void*)(user_buf + len), size - len, PKTDATA(pktlog_ring->dhdp->osh, |
| report_ptr->info.pkt), frame_len); |
| len += frame_len; |
| |
| ret = memcpy_s((void*)(user_buf + len), size - len, buf, bytes_user_data); |
| len += bytes_user_data; |
| |
| dll_delete((dll_t *)report_ptr); |
| PKTFREE(pktlog_ring->dhdp->osh, report_ptr->info.pkt, TRUE); |
| pktlog_ring->pktcount--; |
| dll_append(&pktlog_ring->ring_info_free, (dll_t *)report_ptr); |
| #else |
| ret = dhd_export_debug_data((char*)&report_ptr->info.driver_ts_sec, file, |
| user_buf, sizeof(report_ptr->info.driver_ts_sec), &pos); |
| len += sizeof(report_ptr->info.driver_ts_sec); |
| |
| ret = dhd_export_debug_data((char*)&report_ptr->info.driver_ts_usec, file, |
| user_buf, sizeof(report_ptr->info.driver_ts_usec), &pos); |
| len += sizeof(report_ptr->info.driver_ts_usec); |
| |
| if (report_ptr->info.payload_type == FRAME_TYPE_ETHERNET_II) { |
| frame_len = (uint32)min(report_ptr->info.pkt_len, |
| (size_t)MAX_FRAME_LEN_ETHERNET); |
| |
| } else { |
| frame_len = (uint32)min(report_ptr->info.pkt_len, |
| (size_t)MAX_FRAME_LEN_80211_MGMT); |
| } |
| |
| bytes_user_data = snprintf(buf, sizeof(buf), "%s:%s:%02d:%s:%d.%d s\n", |
| DHD_PKTLOG_FATE_INFO_FORMAT, |
| (report_ptr->tx_fate ? "Failure" : "Succeed"), |
| report_ptr->tx_fate, |
| (report_ptr->info.direction == PKT_TX) ? "TX" : "RX", |
| report_ptr->info.tx_status_ts_sec, |
| report_ptr->info.tx_status_ts_usec); |
| write_frame_len = frame_len + bytes_user_data; |
| |
| /* pcap pkt head has incl_len and orig_len */ |
| ret = dhd_export_debug_data((char*)&write_frame_len, file, user_buf, |
| sizeof(write_frame_len), &pos); |
| len += sizeof(write_frame_len); |
| |
| ret = dhd_export_debug_data((char*)&write_frame_len, file, user_buf, |
| sizeof(write_frame_len), &pos); |
| len += sizeof(write_frame_len); |
| |
| if (pktlog_ring->pktlog_minmize) { |
| dhd_pktlog_minimize_report(PKTDATA(pktlog_ring->dhdp->osh, |
| report_ptr->info.pkt), frame_len, file, user_buf, &pos); |
| } else { |
| ret = dhd_export_debug_data(PKTDATA(pktlog_ring->dhdp->osh, |
| report_ptr->info.pkt), file, user_buf, frame_len, &pos); |
| } |
| len += frame_len; |
| |
| ret = dhd_export_debug_data(buf, file, user_buf, bytes_user_data, &pos); |
| len += bytes_user_data; |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| } |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| DHD_PKT_LOG_UNLOCK(dhd_os_get_pktlog_lock(dhdp), flags); |
| *written_bytes = len; |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| OSL_ATOMIC_SET(dhdp->osh, &pktlog_ring->start, TRUE); |
| |
| return ret; |
| } |
| |
| int |
| dhd_pktlog_dump_write_memory(dhd_pub_t *dhdp, const void *user_buf, uint32 size) |
| { |
| #ifdef DHD_PKT_LOGGING_DBGRING |
| int ret = dhd_pktlog_dump_write(dhdp, NULL, user_buf, &size); |
| #else |
| int ret = dhd_pktlog_dump_write(dhdp, NULL, user_buf, size); |
| #endif /* DHD_PKT_LOGGING_DBGRING */ |
| if (ret < 0) { |
| DHD_ERROR(("dhd_pktlog_dump_write_memory error\n")); |
| } |
| return ret; |
| } |
| |
| int |
| dhd_pktlog_dump_write_file(dhd_pub_t *dhdp) |
| { |
| struct file *w_pcap_fp = NULL; |
| uint32 file_mode; |
| mm_segment_t old_fs; |
| char pktlogdump_path[128]; |
| int ret = BCME_OK; |
| |
| dhd_pktlog_get_filename(dhdp, pktlogdump_path, 128); |
| old_fs = get_fs(); |
| set_fs(KERNEL_DS); |
| file_mode = O_CREAT | O_WRONLY; |
| |
| w_pcap_fp = dhd_filp_open(pktlogdump_path, file_mode, 0664); |
| if (IS_ERR(w_pcap_fp) || (w_pcap_fp == NULL)) { |
| DHD_ERROR(("%s: Couldn't open file '%s' err %ld\n", |
| __FUNCTION__, pktlogdump_path, PTR_ERR(w_pcap_fp))); |
| ret = BCME_ERROR; |
| goto fail; |
| } |
| |
| dhd_pktlog_dump_write(dhdp, w_pcap_fp, NULL, 0); |
| if (ret < 0) { |
| DHD_ERROR(("dhd_pktlog_dump_write error\n")); |
| goto fail; |
| } |
| |
| /* Sync file from filesystem to physical media */ |
| ret = dhd_vfs_fsync(w_pcap_fp, 0); |
| if (ret < 0) { |
| DHD_ERROR(("%s(): sync pcap file error, err = %d\n", __FUNCTION__, ret)); |
| goto fail; |
| } |
| fail: |
| if (!IS_ERR(w_pcap_fp)) { |
| dhd_filp_close(w_pcap_fp, NULL); |
| } |
| |
| set_fs(old_fs); |
| |
| #ifdef DHD_DUMP_MNGR |
| if (ret >= 0) { |
| dhd_dump_file_manage_enqueue(dhdp, pktlogdump_path, DHD_PKTLOG_DUMP_TYPE); |
| } |
| #endif /* DHD_DUMP_MNGR */ |
| return ret; |
| } |
| |
| #ifdef DHD_COMPACT_PKT_LOG |
| static uint64 |
| dhd_cpkt_log_calc_time_diff(dhd_pktlog_ring_info_t *pkt_info, uint64 curr_ts_nsec) |
| { |
| uint64 pkt_ts_nsec = pkt_info->info.driver_ts_sec * NSEC_PER_SEC + |
| pkt_info->info.driver_ts_usec * NSEC_PER_USEC; |
| |
| return (curr_ts_nsec - pkt_ts_nsec) / NSEC_PER_USEC; |
| } |
| |
| static int |
| dhd_cpkt_log_get_ts_idx(dhd_pktlog_t *pktlog, dhd_pktlog_ring_info_t *pkt_info, u64 curr_ts_nsec) |
| { |
| struct rb_node *n = pktlog->cpkt_log_tt_rbt.rb_node; |
| dhd_cpkt_log_ts_node_t *node = NULL; |
| |
| uint64 ts_diff = dhd_cpkt_log_calc_time_diff(pkt_info, curr_ts_nsec); |
| |
| if (ts_diff > dhd_cpkt_log_tt_idx[CPKT_LOG_TT_IDX_ARR_SZ - 1]) |
| return CPKT_LOG_TT_IDX_ARR_SZ; |
| |
| while (n) { |
| node = rb_entry(n, dhd_cpkt_log_ts_node_t, rb); |
| |
| if (ts_diff < node->ts_diff) |
| n = n->rb_left; |
| else if (ts_diff > node->ts_diff) |
| n = n->rb_right; |
| else |
| break; |
| } |
| |
| if (node != NULL) { |
| if (node->idx && ts_diff < node->ts_diff) |
| return node->idx - 1; |
| return node->idx; |
| } |
| |
| return BCME_NOTFOUND; |
| } |
| |
| static int |
| dhd_cpkt_log_get_direction(dhd_pktlog_ring_info_t *pkt_info) |
| { |
| return pkt_info->info.direction == PKTLOG_TXPKT_CASE ? PKT_TX : PKT_RX; |
| } |
| |
| static int |
| dhd_cpkt_log_get_802_1x_subtype(eapol_header_t *eapol) |
| { |
| int subtype; |
| eap_header_t *eap; |
| eapol_wpa_key_header_t *ek; |
| |
| uint16 key_info; |
| int pair, ack, mic, kerr, req, sec, install; |
| |
| subtype = CPKT_LOG_802_1X_SUBTYPE_OTHERS; |
| if (eapol->type != EAPOL_KEY) { |
| eap = (eap_header_t *)eapol->body; |
| |
| switch (eap->type) { |
| case EAP_IDENTITY: |
| subtype = CPKT_LOG_802_1X_SUBTYPE_IDENTITY; |
| break; |
| case REALM_EAP_TLS: |
| subtype = CPKT_LOG_802_1X_SUBTYPE_TLS; |
| break; |
| case REALM_EAP_TTLS: |
| subtype = CPKT_LOG_802_1X_SUBTYPE_TTLS; |
| break; |
| case REALM_EAP_FAST: |
| subtype = CPKT_LOG_802_1X_SUBTYPE_FAST; |
| break; |
| case REALM_EAP_LEAP: |
| subtype = CPKT_LOG_802_1X_SUBTYPE_LEAP; |
| break; |
| case REALM_EAP_PSK: |
| subtype = CPKT_LOG_802_1X_SUBTYPE_PWD; |
| break; |
| case REALM_EAP_SIM: |
| subtype = CPKT_LOG_802_1X_SUBTYPE_SIM; |
| break; |
| case REALM_EAP_AKA: |
| subtype = CPKT_LOG_802_1X_SUBTYPE_AKA; |
| break; |
| case REALM_EAP_AKAP: |
| subtype = CPKT_LOG_802_1X_SUBTYPE_AKAP; |
| break; |
| default: |
| break; |
| } |
| if (eap->code == EAP_SUCCESS) |
| subtype = CPKT_LOG_802_1X_SUBTYPE_SUCCESS; |
| } else { |
| /* in case of 4 way handshake */ |
| ek = (eapol_wpa_key_header_t *)(eapol->body); |
| |
| if (ek->type == EAPOL_WPA2_KEY || ek->type == EAPOL_WPA_KEY) { |
| key_info = ntoh16_ua(&ek->key_info); |
| |
| pair = 0 != (key_info & WPA_KEY_PAIRWISE); |
| ack = 0 != (key_info & WPA_KEY_ACK); |
| mic = 0 != (key_info & WPA_KEY_MIC); |
| kerr = 0 != (key_info & WPA_KEY_ERROR); |
| req = 0 != (key_info & WPA_KEY_REQ); |
| sec = 0 != (key_info & WPA_KEY_SECURE); |
| install = 0 != (key_info & WPA_KEY_INSTALL); |
| |
| if (!sec && !mic && ack && !install && pair && !kerr && !req) |
| subtype = CPKT_LOG_802_1X_SUBTYPE_4WAY_M1; |
| else if (pair && !install && !ack && mic && !sec && !kerr && !req) |
| subtype = CPKT_LOG_802_1X_SUBTYPE_4WAY_M2; |
| else if (pair && ack && mic && sec && !kerr && !req) |
| subtype = CPKT_LOG_802_1X_SUBTYPE_4WAY_M3; |
| else if (pair && !install && !ack && mic && sec && !req && !kerr) |
| subtype = CPKT_LOG_802_1X_SUBTYPE_4WAY_M4; |
| } |
| } |
| |
| return subtype; |
| } |
| |
| static int |
| dhd_cpkt_log_get_pkt_info(dhd_pktlog_t *pktlog, dhd_pktlog_ring_info_t *pkt_info) |
| { |
| int type; |
| int subtype = 0; |
| |
| uint8 prot; |
| uint16 src_port, dst_port; |
| int len, offset; |
| |
| uint8 *pdata; |
| uint8 *pkt_data; |
| |
| uint16 eth_type; |
| struct bcmarp *arp; |
| struct bcmicmp_hdr *icmp; |
| struct ipv4_hdr *ipv4; |
| struct ether_header *eth_hdr; |
| bcm_tlv_t *dhcp_opt; |
| |
| struct ipv6_hdr *ipv6; |
| struct icmp6_hdr *icmpv6_hdr; |
| |
| pkt_data = (uint8 *)PKTDATA(pktlog->dhdp->osh, pkt_info->info.pkt); |
| |
| eth_hdr = (struct ether_header *)pkt_data; |
| eth_type = ntoh16(eth_hdr->ether_type); |
| |
| type = CPKT_LOG_TYPE_OTHERS; |
| switch (eth_type) { |
| case ETHER_TYPE_IP: |
| if (get_pkt_ip_type(pktlog->dhdp->osh, pkt_info->info.pkt, |
| &pdata, &len, &prot) != 0) { |
| DHD_PKT_LOG(("%s: fail to get pkt ip type\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| if (prot == IP_PROT_ICMP) { |
| icmp = (struct bcmicmp_hdr *)(pdata); |
| if (!(icmp->type == ICMP_TYPE_ECHO_REQUEST || |
| icmp->type == ICMP_TYPE_ECHO_REPLY || |
| icmp->type == CPKT_LOG_ICMP_TYPE_DEST_UNREACHABLE)) { |
| return BCME_ERROR; |
| } |
| |
| if (icmp->type == ICMP_TYPE_ECHO_REQUEST) { |
| type = CPKT_LOG_TYPE_ICMP_REQ; |
| /* Subtype = Last 8 bits of identifier */ |
| subtype = ntoh16_ua(pdata + sizeof(*icmp)) & 0xFF; |
| } else if (icmp->type == ICMP_TYPE_ECHO_REPLY) { |
| type = CPKT_LOG_TYPE_ICMP_RES; |
| /* Subtype = Last 8 bits of identifier */ |
| subtype = ntoh16_ua(pdata + sizeof(*icmp)) & 0xFF; |
| } else if (icmp->type == CPKT_LOG_ICMP_TYPE_DEST_UNREACHABLE) { |
| type = CPKT_LOG_TYPE_ICMP_UNREACHABLE; |
| /* Subtype = Last 8 bits of identifier */ |
| ipv4 = (struct ipv4_hdr *)(pdata + sizeof(*icmp) + |
| CPKT_LOG_ICMP_TYPE_DEST_UNREACHABLE_IPV4_OFFSET); |
| subtype = ipv4->id & 0xFF; |
| } |
| |
| DHD_PKT_LOG(("%s: type = ICMP(%d), subtype = %x \n", |
| __FUNCTION__, type, subtype)); |
| } else if (prot == IP_PROT_UDP) { |
| if (len < UDP_HDR_LEN) |
| return BCME_ERROR; |
| |
| src_port = ntoh16_ua(pdata); |
| dst_port = ntoh16_ua(pdata + UDP_DEST_PORT_OFFSET); |
| |
| if (src_port == DHCP_PORT_SERVER || src_port == DHCP_PORT_CLIENT) { |
| type = CPKT_LOG_TYPE_DHCP; |
| /* Subtype = DHCP message type */ |
| offset = DHCP_OPT_OFFSET + CPKT_LOG_DHCP_MAGIC_COOKIE_LEN; |
| if ((UDP_HDR_LEN + offset) >= len) |
| return BCME_ERROR; |
| len -= (UDP_HDR_LEN - offset); |
| |
| dhcp_opt = bcm_parse_tlvs(pdata + UDP_HDR_LEN + offset, |
| len, DHCP_OPT_MSGTYPE); |
| if (dhcp_opt == NULL) |
| return BCME_NOTFOUND; |
| subtype = dhcp_opt->data[0]; |
| |
| DHD_PKT_LOG(("%s: type = DHCP(%d), subtype = %x \n", |
| __FUNCTION__, type, subtype)); |
| } else if (src_port == CPKT_LOG_DNS_PORT_CLIENT || |
| dst_port == CPKT_LOG_DNS_PORT_CLIENT || |
| dst_port == CPKT_LOG_MDNS_PORT_CLIENT) { |
| type = CPKT_LOG_TYPE_DNS; |
| /* Subtype = Last 8 bits of DNS Transaction ID */ |
| subtype = ntoh16_ua(pdata + UDP_HDR_LEN) & 0xFF; |
| |
| DHD_PKT_LOG(("%s: type = DNS(%d), subtype = %x \n", |
| __FUNCTION__, type, subtype)); |
| } else { |
| DHD_PKT_LOG(("%s: unsupported ports num (src:%d, dst:%d)\n", |
| __FUNCTION__, src_port, dst_port)); |
| } |
| } else { |
| DHD_PKT_LOG(("%s: prot = %x\n", __FUNCTION__, prot)); |
| } |
| |
| break; |
| case ETHER_TYPE_ARP: |
| type = CPKT_LOG_TYPE_ARP; |
| /* Subtype = Last 8 bits of target IP address */ |
| arp = (struct bcmarp *)(pkt_data + ETHER_HDR_LEN); |
| subtype = arp->dst_ip[IPV4_ADDR_LEN - 1]; |
| |
| DHD_PKT_LOG(("%s: type = ARP(%d), subtype = %x\n", |
| __FUNCTION__, type, subtype)); |
| |
| break; |
| case ETHER_TYPE_802_1X: |
| type = CPKT_LOG_TYPE_802_1X; |
| /* EAPOL for 802.3/Ethernet */ |
| subtype = dhd_cpkt_log_get_802_1x_subtype((eapol_header_t *)pkt_data); |
| |
| DHD_PKT_LOG(("%s: type = 802.1x(%d), subtype = %x\n", |
| __FUNCTION__, type, subtype)); |
| |
| break; |
| case ETHER_TYPE_IPV6: |
| ipv6 = (struct ipv6_hdr *)(pkt_data + ETHER_HDR_LEN); |
| if (ipv6->nexthdr == ICMPV6_HEADER_TYPE) { |
| type = CPKT_LOG_TYPE_ICMPv6; |
| icmpv6_hdr = |
| (struct icmp6_hdr *)(pkt_data + ETHER_HDR_LEN + sizeof(*ipv6)); |
| subtype = icmpv6_hdr->icmp6_type; |
| |
| DHD_PKT_LOG(("%s: type = ICMPv6(%x), subtype = %x\n", |
| __FUNCTION__, type, subtype)); |
| } else { |
| DHD_ERROR(("%s: unsupported ipv6 next header\n", __FUNCTION__)); |
| } |
| |
| break; |
| default: |
| DHD_ERROR(("%s: Invalid eth type (%x)\n", __FUNCTION__, eth_hdr->ether_type)); |
| break; |
| } |
| |
| return (subtype << CPKT_LOG_BIT_LEN_TYPE) | type; |
| } |
| |
| static int |
| dhd_cpkt_log_get_pkt_fate(dhd_pktlog_ring_info_t *pktlog_info) |
| { |
| return pktlog_info->fate; |
| } |
| |
| /* |
| * dhd_cpkt_log_build: prepare 22 bits of data as compact packet log format to report to big data |
| * |
| * pkt_info: one packet data from packet log |
| * curr_ts_nsec: current time (nano seconds) |
| * cpkt: pointer for output(22 bits compact packet log) |
| * |
| */ |
| static int |
| dhd_cpkt_log_build(dhd_pktlog_t *pktlog, dhd_pktlog_ring_info_t *pkt_info, |
| u64 curr_ts_nsec, int *cpkt) |
| { |
| int ret; |
| int mask; |
| int temp = 0; |
| |
| /* Timestamp index */ |
| ret = dhd_cpkt_log_get_ts_idx(pktlog, pkt_info, curr_ts_nsec); |
| if (ret < 0) { |
| DHD_ERROR(("%s: Invalid cpktlog ts, err = %d\n", __FUNCTION__, ret)); |
| return ret; |
| } |
| mask = CPKT_LOG_BIT_MASK_TS; |
| temp |= ((ret & mask) << CPKT_LOG_BIT_OFFSET_TS); |
| |
| /* Direction: Tx/Rx */ |
| ret = dhd_cpkt_log_get_direction(pkt_info); |
| mask = CPKT_LOG_BIT_MASK_DIR; |
| temp |= ((ret & mask) << CPKT_LOG_BIT_OFFSET_DIR); |
| |
| /* Info = Packet Type & Packet Subtype */ |
| ret = dhd_cpkt_log_get_pkt_info(pktlog, pkt_info); |
| if (ret < 0) { |
| DHD_ERROR(("%s: Invalid cpktlog info, err = %d\n", __FUNCTION__, ret)); |
| return ret; |
| } |
| mask = CPKT_LOG_BIT_MASK_SUBTYPE << CPKT_LOG_BIT_LEN_TYPE | CPKT_LOG_BIT_MASK_TYPE; |
| temp |= ((ret & mask) << CPKT_LOG_BIT_OFFSET_TYPE); |
| |
| /* Packet Fate */ |
| ret = dhd_cpkt_log_get_pkt_fate(pkt_info); |
| mask = CPKT_LOG_BIT_MASK_PKT_FATE; |
| temp |= ((ret & mask) << CPKT_LOG_BIT_OFFSET_PKT_FATE); |
| |
| *cpkt = temp; |
| |
| return BCME_OK; |
| } |
| |
| int |
| dhd_cpkt_log_proc(dhd_pub_t *dhdp, char *buf, int buf_len, int bit_offset, int req_pkt_num) |
| { |
| int ret; |
| int cpkt; |
| int offset = bit_offset; |
| dll_t *item_p, *prev_p; |
| |
| uint8 pkt_cnt; |
| u64 curr_ts_nsec; |
| |
| dhd_pktlog_t *pktlog; |
| dhd_pktlog_ring_t *pktlog_rbuf; |
| |
| if (!dhdp || !dhdp->pktlog) { |
| DHD_ERROR(("%s: dhdp or pktlog is NULL\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| if (!dhdp->pktlog->pktlog_ring) { |
| DHD_ERROR(("%s: pktlog_ring is NULL\n", __FUNCTION__)); |
| return BCME_ERROR; |
| } |
| |
| DHD_PKT_LOG(("%s: start cpkt log\n", __FUNCTION__)); |
| |
| pktlog = dhdp->pktlog; |
| pktlog_rbuf = pktlog->pktlog_ring; |
| |
| req_pkt_num = req_pkt_num > CPKT_LOG_MAX_NUM ? |
| CPKT_LOG_MAX_NUM : req_pkt_num; |
| |
| pkt_cnt = 0; |
| curr_ts_nsec = local_clock(); |
| for (item_p = dll_tail_p(&pktlog_rbuf->ring_info_head); |
| !dll_end(&pktlog_rbuf->ring_info_head, item_p); |
| item_p = prev_p) { |
| prev_p = dll_prev_p(item_p); |
| if (prev_p == NULL) |
| break; |
| |
| ret = dhd_cpkt_log_build(pktlog, (dhd_pktlog_ring_info_t *)item_p, |
| curr_ts_nsec, &cpkt); |
| if (ret < 0) |
| continue; |
| |
| offset = dhd_bit_pack(buf, buf_len, offset, cpkt, CPKT_LOG_BIT_SIZE); |
| |
| pkt_cnt++; |
| if (pkt_cnt >= req_pkt_num) |
| break; |
| } |
| |
| return offset; |
| } |
| |
| static void |
| dhd_cpkt_log_insert_ts(dhd_cpkt_log_ts_node_t *node, struct rb_root *root) |
| { |
| struct rb_node **new = &root->rb_node, *parent = NULL; |
| u64 ts_diff = node->ts_diff; |
| |
| while (*new) { |
| parent = *new; |
| if (ts_diff < rb_entry(parent, dhd_cpkt_log_ts_node_t, rb)->ts_diff) |
| new = &parent->rb_left; |
| else |
| new = &parent->rb_right; |
| } |
| |
| rb_link_node(&node->rb, parent, new); |
| rb_insert_color(&node->rb, root); |
| } |
| |
| static void |
| dhd_cpkt_log_deinit_tt(dhd_pub_t *dhdp) |
| { |
| struct rb_node *n; |
| dhd_pktlog_t *pktlog = dhdp->pktlog; |
| |
| dhd_cpkt_log_ts_node_t *node; |
| |
| while ((n = rb_first(&pktlog->cpkt_log_tt_rbt))) { |
| node = rb_entry(n, dhd_cpkt_log_ts_node_t, rb); |
| rb_erase(&node->rb, &pktlog->cpkt_log_tt_rbt); |
| MFREE(dhdp->osh, node, sizeof(*node)); |
| } |
| } |
| |
| static int |
| dhd_cpkt_log_init_tt(dhd_pub_t *dhdp) |
| { |
| int i; |
| int ret = BCME_OK; |
| |
| dhd_pktlog_t *pktlog = dhdp->pktlog; |
| |
| dhd_cpkt_log_ts_node_t *node; |
| |
| for (i = 0; i < ARRAYSIZE(dhd_cpkt_log_tt_idx); i++) { |
| node = (dhd_cpkt_log_ts_node_t *)MALLOCZ(dhdp->osh, sizeof(*node)); |
| if (!node) { |
| ret = BCME_NOMEM; |
| goto exit; |
| } |
| node->ts_diff = dhd_cpkt_log_tt_idx[i]; |
| node->idx = i; |
| |
| dhd_cpkt_log_insert_ts(node, &pktlog->cpkt_log_tt_rbt); |
| } |
| |
| return BCME_OK; |
| exit: |
| dhd_cpkt_log_deinit_tt(dhdp); |
| |
| return ret; |
| } |
| #endif /* DHD_COMPACT_PKT_LOG */ |
| #endif /* DHD_PKT_LOGGING */ |