| /* |
| * Copyright (c) 2019-2020 The Linux Foundation. All rights reserved. |
| * |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all |
| * copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL |
| * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE |
| * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL |
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR |
| * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER |
| * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| * PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| /** |
| * file: peer rate statitistics application |
| * This file provides framework to display peer rate statistics |
| */ |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <qcatools_lib.h> |
| #include <dp_rate_stats_pub.h> |
| |
| #ifndef min |
| #define min(x, y) ((x) < (y) ? (x) : (y)) |
| #else |
| #error confilicting defs of min |
| #endif |
| |
| #define PRINT(fmt, ...) \ |
| do { \ |
| printf(fmt, ##__VA_ARGS__); \ |
| printf("\n"); \ |
| } while (0) |
| |
| char interface[IFNAMSIZ]; |
| |
| static void dp_peer_rx_rate_stats_print(uint8_t *peer_mac, |
| uint64_t peer_cookie, |
| void *buffer, |
| uint32_t buffer_len) |
| { |
| int i = 0; |
| struct wlan_rx_rate_stats *rx_stats; |
| uint8_t is_lithium; |
| uint8_t chain, max_chain, bw, max_bw; |
| struct wlan_rx_rate_stats *tmp_rx_stats;; |
| |
| rx_stats = tmp_rx_stats = (struct wlan_rx_rate_stats *)buffer; |
| PRINT("\n......................................"); |
| PRINT("......................................"); |
| PRINT("PEER %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", |
| peer_mac[0], |
| peer_mac[1], |
| peer_mac[2], |
| peer_mac[3], |
| peer_mac[4], |
| peer_mac[5]); |
| PRINT("\tpeer cookie: %016"PRIx64"\n", |
| (peer_cookie & 0xFFFFFFFF00000000) >> |
| WLANSTATS_PEER_COOKIE_LSB); |
| is_lithium = (peer_cookie & WLANSTATS_COOKIE_PLATFORM_OFFSET) |
| >> WLANSTATS_PEER_COOKIE_LSB; |
| if (is_lithium) { |
| max_chain = 8; |
| max_bw = 8; |
| } else { |
| max_chain = 4; |
| max_bw = 4; |
| } |
| PRINT("\n.............................................."); |
| PRINT("................................"); |
| PRINT("................................................"); |
| PRINT(".................................\n"); |
| PRINT("\tRx statistics:"); |
| PRINT(" %10s | %10s | %10s | %10s | %10s | %10s|", |
| "rate", |
| "rix", |
| "bytes", |
| "msdus", |
| "mpdus", |
| "ppdus"); |
| PRINT(" %10s | %10s | %10s |", |
| "retries", |
| "sgi", |
| "rssi\n"); |
| |
| for (i = 0; i < WLANSTATS_CACHE_SIZE; i++) { |
| if (rx_stats->rix != INVALID_CACHE_IDX) { |
| PRINT(" %10u | %10u | %10u | %10u | %10u | %10u |", |
| rx_stats->rate, |
| GET_DP_PEER_STATS_RIX(rx_stats->rix), |
| rx_stats->num_bytes, |
| rx_stats->num_msdus, |
| rx_stats->num_mpdus, |
| rx_stats->num_ppdus); |
| PRINT(" %10u | %10u | %10lu |\n", |
| rx_stats->num_retries, |
| rx_stats->num_sgi, |
| rx_stats->avg_rssi); |
| } |
| rx_stats = rx_stats + 1; |
| } |
| if (is_lithium) { |
| PRINT("\n %10s | %10s | %10s | %10s | %10s |", |
| "rate", |
| "rssi 1 p20", |
| "rssi 1 e20", |
| "rssi 1 e40 low20", |
| "rssi 1 e40 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |", |
| "rssi 1 ext80 low20", |
| "rssi 1 ext80 low_high20", |
| "rssi 1 ext80 high_low20", |
| "rssi 1 ext80 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |", |
| "rssi 2 p20", |
| "rssi 2 e20", |
| "rssi 2 e40 low20", |
| "rssi 2 e40 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |", |
| "rssi 2 ext80 low20", |
| "rssi 2 ext80 low_high20", |
| "rssi 2 ext80 high_low20", |
| "rssi 2 ext80 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |", |
| "rssi 3 p20", |
| "rssi 3 e20", |
| "rssi 3 e40 low20", |
| "rssi 3 e40 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |", |
| "rssi 3 ext80 low20", |
| "rssi 3 ext80 low_high20", |
| "rssi 3 ext80 high_low20", |
| "rssi 3 ext80 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |", |
| "rssi 4 p20", |
| "rssi 4 e20", |
| "rssi 4 e40 low20", |
| "rssi 4 e40 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |", |
| "rssi 4 ext80 low20", |
| "rssi 4 ext80 low_high20", |
| "rssi 4 ext80 high_low20", |
| "rssi 4 ext80 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |", |
| "rssi 5 p20", |
| "rssi 5 e20", |
| "rssi 5 e40 low20", |
| "rssi 5 e40 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |", |
| "rssi 5 ext80 low20", |
| "rssi 5 ext80 low_high20", |
| "rssi 5 ext80 high_low20", |
| "rssi 5 ext80 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |", |
| "rssi 6 p20", |
| "rssi 6 e20", |
| "rssi 6 e40 low20", |
| "rssi 6 e40 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |", |
| "rssi 6 ext80 low20", |
| "rssi 6 ext80 low_high20", |
| "rssi 6 ext80 high_low20", |
| "rssi 6 ext80 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |", |
| "rssi 7 p20", |
| "rssi 7 e20", |
| "rssi 7 e40 low20", |
| "rssi 7 e40 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |", |
| "rssi 7 ext80 low20", |
| "rssi 7 ext80 low_high20", |
| "rssi 7 ext80 high_low20", |
| "rssi 7 ext80 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |", |
| "rssi 8 p20", |
| "rssi 8 e20", |
| "rssi 8 e40 low20", |
| "rssi 8 e40 high20"); |
| PRINT("\n | %10s | %10s | %10s | %10s |\n\n\n", |
| "rssi 8 ext80 low20", |
| "rssi 8 ext80 low_high20", |
| "rssi 8 ext80 high_low20", |
| "rssi 8 ext80 high20"); |
| } else { |
| PRINT("\n %10s | %10s | %10s | %10s | %10s |", |
| "rate", |
| "rssi 1 p20", |
| "rssi 1 e20", |
| "rssi 1 e40", |
| "rssi 1 e80"); |
| PRINT(" | %10s | %10s | %10s | %10s |", |
| "rssi 2 p20", |
| "rssi 2 e20", |
| "rssi 2 e40", |
| "rssi 2 e80"); |
| PRINT(" | %10s | %10s | %10s | %10s |", |
| "rssi 3 p20", |
| "rssi 3 e20", |
| "rssi 3 e40", |
| "rssi 3 e80"); |
| PRINT(" | %10s | %10s | %10s | %10s |\n\n\n", |
| "rssi 4 p20", |
| "rssi 4 e20", |
| "rssi 4 e40", |
| "rssi 4 e80"); |
| } |
| for (i = 0; i < WLANSTATS_CACHE_SIZE; i++) { |
| if (tmp_rx_stats->rix != INVALID_CACHE_IDX) { |
| printf(" %10u |", tmp_rx_stats->rate); |
| for (chain = 0; chain < max_chain; chain++) { |
| for (bw = 0; bw < max_bw; bw++) { |
| printf(" %10lu |", |
| tmp_rx_stats->avg_rssi_ant[chain][bw]); |
| } |
| printf(" \n\t "); |
| } |
| PRINT(" "); |
| } |
| tmp_rx_stats = tmp_rx_stats + 1; |
| } |
| } |
| |
| static void |
| dp_peer_tx_sojourn_stats_print(uint8_t *peer_mac, |
| uint64_t peer_cookie, |
| struct wlan_tx_sojourn_stats *sojourn_stats) |
| { |
| uint8_t tid; |
| |
| PRINT("\n.........................................."); |
| PRINT("...................................."); |
| PRINT("...................................."); |
| PRINT(".........................................\n"); |
| PRINT("PEER %02hhx:%02hhx:%02hhx:%02hhx%02hhx:%02hhx\n", |
| peer_mac[0], |
| peer_mac[1], |
| peer_mac[2], |
| peer_mac[3], |
| peer_mac[4], |
| peer_mac[5]); |
| PRINT("\tPEER Cookie: %016"PRIx64"\n", peer_cookie); |
| PRINT("\n..........................................."); |
| PRINT("..................................."); |
| PRINT(".................................."); |
| PRINT("............................................"); |
| PRINT("\n\tSojourn statistics:\n"); |
| PRINT("\t\t%10s %10s %20s %20s\n", "tid", "ave", "sum", "num"); |
| |
| for (tid = 0; tid < WLAN_DATA_TID_MAX; tid++) { |
| /* change sum_sojourn_msdu data type to u64 */ |
| PRINT("\t\t%10d %10lu %20u %20u\n", |
| tid, |
| sojourn_stats->avg_sojourn_msdu[tid], |
| sojourn_stats->sum_sojourn_msdu[tid], |
| sojourn_stats->num_msdus[tid]); |
| } |
| #ifdef __LP64__ |
| PRINT("sizeof(avg) : %"PRIu64, |
| #else |
| PRINT("sizeof(avg) : %"PRIu32, |
| #endif |
| sizeof(sojourn_stats->avg_sojourn_msdu[tid])); |
| PRINT("\n..........................................."); |
| PRINT("..................................."); |
| PRINT("..................................."); |
| PRINT("...........................................\n"); |
| } |
| |
| static void dp_peer_tx_rate_stats_print(uint8_t *peer_mac, |
| uint64_t peer_cookie, |
| void *buffer, |
| uint32_t buffer_len) |
| { |
| int i = 0; |
| struct wlan_tx_rate_stats *tx_stats; |
| struct wlan_tx_sojourn_stats *sojourn_stats; |
| |
| if (buffer_len < (WLANSTATS_CACHE_SIZE * |
| sizeof(struct wlan_tx_rate_stats)) |
| + sizeof(struct wlan_tx_sojourn_stats)) { |
| PRINT("invalid buffer len, return"); |
| return; |
| } |
| tx_stats = (struct wlan_tx_rate_stats *)buffer; |
| PRINT("\n..........................................."); |
| PRINT("..................................."); |
| PRINT("..................................."); |
| PRINT("...........................................\n"); |
| PRINT("PEER %02hhx:%02hhx:%02hhx:%02hhx%02hhx:%02hhx\n\n", |
| peer_mac[0], |
| peer_mac[1], |
| peer_mac[2], |
| peer_mac[3], |
| peer_mac[4], |
| peer_mac[5]); |
| PRINT("\tPEER Cookie: %016"PRIx64, peer_cookie); |
| PRINT("\n..........................................."); |
| PRINT("..................................."); |
| PRINT("..................................."); |
| PRINT("...........................................\n"); |
| PRINT("\tTx statistics:\n"); |
| PRINT("\t\t%10s | %10s | %10s | %10s | %10s", |
| "rate", |
| "rix", |
| "attempts", |
| "success", |
| "ppdus"); |
| PRINT(" %10s | %10s | %10s |", |
| "msdus", |
| "bytes", |
| "retries\n"); |
| |
| for (i = 0; i < WLANSTATS_CACHE_SIZE; i++) { |
| if (tx_stats->rix != INVALID_CACHE_IDX) { |
| PRINT("\t\t%10u | %10u | %10u | %10u | %10u\n", |
| tx_stats->rate, |
| GET_DP_PEER_STATS_RIX(tx_stats->rix), |
| tx_stats->mpdu_attempts, |
| tx_stats->mpdu_success, |
| tx_stats->num_ppdus); |
| PRINT(" %10u | %10u | %10u |\n", |
| tx_stats->num_msdus, |
| tx_stats->num_bytes, |
| tx_stats->num_retries); |
| } |
| tx_stats = tx_stats + 1; |
| } |
| |
| sojourn_stats = (struct wlan_tx_sojourn_stats *)((uint8_t *)buffer |
| + (WLANSTATS_CACHE_SIZE * |
| sizeof(struct wlan_tx_rate_stats))); |
| dp_peer_tx_sojourn_stats_print(peer_mac, peer_cookie, sojourn_stats); |
| |
| return; |
| } |
| |
| static void dp_peer_tx_link_stats_print(uint8_t *peer_mac, |
| uint64_t peer_cookie, |
| void *buffer, |
| uint32_t buffer_len) |
| { |
| struct wlan_tx_link_stats *tx_stats; |
| uint8_t is_lithium; |
| |
| is_lithium = (peer_cookie & WLANSTATS_COOKIE_PLATFORM_OFFSET) |
| >> WLANSTATS_PEER_COOKIE_LSB; |
| |
| if (!is_lithium) { |
| PRINT("Not supported in non-lithium platforms\n"); |
| return; |
| } |
| |
| if (buffer_len < sizeof(struct wlan_tx_link_stats)) { |
| PRINT("invalid buffer len, return"); |
| return; |
| } |
| |
| tx_stats = (struct wlan_tx_link_stats *)buffer; |
| |
| PRINT("\n\n"); |
| PRINT("========= PEER TX LINK QUALITY METRICS =========\n"); |
| PRINT("PEER %02hhx:%02hhx:%02hhx:%02hhx%02hhx:%02hhx", |
| peer_mac[0], |
| peer_mac[1], |
| peer_mac[2], |
| peer_mac[3], |
| peer_mac[4], |
| peer_mac[5]); |
| PRINT("num_ppdus: %u", tx_stats->num_ppdus); |
| PRINT("bytes: %"PRIu64, tx_stats->bytes); |
| PRINT("phy_rate_actual_su: %u kbps", tx_stats->phy_rate_actual_su); |
| PRINT("phy_rate_actual_mu: %u kbps", tx_stats->phy_rate_actual_mu); |
| PRINT("ofdma_usage: %u", tx_stats->ofdma_usage); |
| PRINT("mu_mimo_usage: %u", tx_stats->mu_mimo_usage); |
| PRINT("bw_usage_avg: %u MHz", tx_stats->bw.usage_avg); |
| PRINT("bw_usage_packets: 20MHz: %u 40MHz: %u 80MHz: %u 160MHz: %u", |
| tx_stats->bw.usage_counter[0], tx_stats->bw.usage_counter[1], |
| tx_stats->bw.usage_counter[2], tx_stats->bw.usage_counter[3]); |
| PRINT("bw_usage_max:: %u%%", tx_stats->bw.usage_max); |
| PRINT("ack_rssi: %lu", tx_stats->ack_rssi); |
| PRINT("pkt_error_rate: %u%%", tx_stats->pkt_error_rate); |
| } |
| |
| static void dp_peer_rx_link_stats_print(uint8_t *peer_mac, |
| uint64_t peer_cookie, |
| void *buffer, |
| uint32_t buffer_len) |
| { |
| struct wlan_rx_link_stats *rx_stats; |
| uint8_t is_lithium; |
| |
| is_lithium = (peer_cookie & WLANSTATS_COOKIE_PLATFORM_OFFSET) |
| >> WLANSTATS_PEER_COOKIE_LSB; |
| |
| if (!is_lithium) { |
| PRINT("Not supported in non-lithium platforms\n"); |
| return; |
| } |
| |
| if (buffer_len < sizeof(struct wlan_rx_link_stats)) { |
| PRINT("invalid buffer len, return"); |
| return; |
| } |
| |
| rx_stats = (struct wlan_rx_link_stats *)buffer; |
| |
| PRINT("\n\n"); |
| PRINT("========= PEER RX LINK QUALITY METRICS =========\n"); |
| PRINT("PEER %02hhx:%02hhx:%02hhx:%02hhx%02hhx:%02hhx", |
| peer_mac[0], |
| peer_mac[1], |
| peer_mac[2], |
| peer_mac[3], |
| peer_mac[4], |
| peer_mac[5]); |
| PRINT("num_ppdus: %u", rx_stats->num_ppdus); |
| PRINT("bytes: %"PRIu64, rx_stats->bytes); |
| PRINT("phy_rate_actual_su: %u kbps", rx_stats->phy_rate_actual_su); |
| PRINT("phy_rate_actual_mu: %u kbps", rx_stats->phy_rate_actual_mu); |
| PRINT("ofdma_usage: %u", rx_stats->ofdma_usage); |
| PRINT("mu_mimo_usage: %u", rx_stats->mu_mimo_usage); |
| PRINT("bw_usage_avg: %u MHz", rx_stats->bw.usage_avg); |
| PRINT("bw_usage_packets: 20MHz: %u 40MHz: %u 80MHz: %u 160MHz: %u", |
| rx_stats->bw.usage_counter[0], rx_stats->bw.usage_counter[1], |
| rx_stats->bw.usage_counter[2], rx_stats->bw.usage_counter[3]); |
| PRINT("bw_usage_max: %u%%", rx_stats->bw.usage_max); |
| PRINT("su_rssi: %lu", rx_stats->su_rssi); |
| PRINT("pkt_error_rate: %u%%", rx_stats->pkt_error_rate); |
| } |
| |
| static void dp_peer_stats_handler(uint32_t cache_type, |
| uint8_t *peer_mac, |
| uint64_t peer_cookie, |
| void *buffer, |
| uint32_t buffer_len) |
| { |
| switch (cache_type) { |
| case DP_PEER_RX_RATE_STATS: |
| dp_peer_rx_rate_stats_print(peer_mac, peer_cookie, |
| buffer, buffer_len); |
| break; |
| case DP_PEER_TX_RATE_STATS: |
| dp_peer_tx_rate_stats_print(peer_mac, peer_cookie, |
| buffer, buffer_len); |
| break; |
| case DP_PEER_TX_LINK_STATS: |
| dp_peer_tx_link_stats_print(peer_mac, peer_cookie, |
| buffer, buffer_len); |
| break; |
| case DP_PEER_RX_LINK_STATS: |
| dp_peer_rx_link_stats_print(peer_mac, peer_cookie, |
| buffer, buffer_len); |
| break; |
| } |
| } |
| |
| static void |
| dp_peer_stats_event_callback(char *ifname, |
| uint32_t cmdid, |
| uint8_t *data, |
| size_t len) |
| { |
| struct nlattr *tb_array[QCA_WLAN_VENDOR_ATTR_PEER_STATS_CACHE_MAX + 1]; |
| struct nlattr *tb; |
| void *buffer = NULL; |
| uint32_t buffer_len = 0; |
| uint8_t *peer_mac; |
| uint32_t cache_type; |
| uint64_t peer_cookie; |
| |
| if (cmdid != QCA_NL80211_VENDOR_SUBCMD_PEER_STATS_CACHE_FLUSH) { |
| /* ignore anyother events*/ |
| return; |
| } |
| |
| if (strncmp(interface, ifname, sizeof(interface)) != 0) { |
| /* ignore events for other interfaces*/ |
| return; |
| } |
| |
| if (nla_parse(tb_array, QCA_WLAN_VENDOR_ATTR_PEER_STATS_CACHE_MAX, |
| (struct nlattr *)data, len, NULL)) { |
| PRINT("Invalid event\n"); |
| return; |
| } |
| |
| tb = tb_array[QCA_WLAN_VENDOR_ATTR_PEER_STATS_CACHE_TYPE]; |
| if (!tb) { |
| PRINT("Cache type in NULL, return"); |
| return; |
| } |
| cache_type = nla_get_u32(tb); |
| |
| tb = tb_array[QCA_WLAN_VENDOR_ATTR_PEER_STATS_CACHE_PEER_MAC]; |
| if (!tb) { |
| PRINT("Peer mac addr is null, return"); |
| return; |
| } |
| peer_mac = (uint8_t *)nla_data(tb); |
| |
| tb = tb_array[QCA_WLAN_VENDOR_ATTR_PEER_STATS_CACHE_DATA]; |
| if (tb) { |
| buffer = (void *)nla_data(tb); |
| buffer_len = nla_len(tb); |
| } |
| |
| tb = tb_array[QCA_WLAN_VENDOR_ATTR_PEER_STATS_CACHE_PEER_COOKIE]; |
| if (!tb) { |
| PRINT("peer cookie attribute is null, return"); |
| return; |
| } |
| peer_cookie = nla_get_u64(tb); |
| if (!buffer) { |
| PRINT(" stats buffer is null, return"); |
| return; |
| } |
| |
| dp_peer_stats_handler(cache_type, peer_mac, peer_cookie, |
| buffer, buffer_len); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int err = 0; |
| wifi_cfg80211_context cfg80211_ctxt; |
| char *ifname; |
| int num_msecs = 0; |
| int status = 0; |
| |
| if (argc < 2) { |
| fprintf(stderr, "Invalid commands args\n"); |
| return -EIO; |
| } |
| |
| /* Reset the cfg80211 context to 0 if the application does not pass |
| * custom private event and command sockets. In this case, the default |
| * port is used for netlink communication. |
| */ |
| memset(&cfg80211_ctxt, 0, sizeof(wifi_cfg80211_context)); |
| |
| ifname = argv[1]; |
| memcpy(interface, ifname, sizeof(interface)); |
| cfg80211_ctxt.event_callback = dp_peer_stats_event_callback; |
| |
| err = wifi_init_nl80211(&cfg80211_ctxt); |
| if (err) { |
| fprintf(stderr, "unable to create NL socket\n"); |
| return -EIO; |
| } |
| |
| /* Starting event thread to listen for responses*/ |
| if (wifi_nl80211_start_event_thread(&cfg80211_ctxt)) { |
| fprintf(stderr, "Unable to setup nl80211 event thread\n"); |
| status = -EIO; |
| goto cleanup; |
| } |
| |
| while (true) { |
| /*sleep for 1 ms*/ |
| usleep(1000); |
| num_msecs++; |
| } |
| |
| wifi_destroy_nl80211(&cfg80211_ctxt); |
| return 0; |
| |
| cleanup: |
| wifi_destroy_nl80211(&cfg80211_ctxt); |
| return status; |
| } |