/*
 * Copyright (c) 2012-2015, 2018 The Linux Foundation. All rights reserved.
 *
 * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
 *
 *
 * 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.
 */

/*
 * This file was originally distributed by Qualcomm Atheros, Inc.
 * under proprietary terms before Copyright ownership was assigned
 * to the Linux Foundation.
 */

/**
 * @file htt_fw_stats.c
 * @brief Provide functions to process FW status retrieved from FW.
 */

#include <htc_api.h>         /* HTC_PACKET */
#include <htt.h>             /* HTT_T2H_MSG_TYPE, etc. */
#include <adf_nbuf.h>        /* adf_nbuf_t */
#include <adf_os_mem.h>      /* adf_os_mem_set */
#include <ol_fw_tx_dbg.h>    /* ol_fw_tx_dbg_ppdu_base */

#include <ol_htt_rx_api.h>
#include <ol_txrx_htt_api.h> /* htt_tx_status */

#include <htt_internal.h>

#include <wlan_defs.h>

#define ROUND_UP_TO_4(val) (((val) + 3) & ~0x3)

static void htt_t2h_stats_tx_rate_stats_print(
    wlan_dbg_tx_rate_info_t *tx_rate_info, int concise)
{
    adf_os_print("TX Rate Info:\n");

    /* MCS */
    adf_os_print("MCS counts (0..9): ");
    adf_os_print("%d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n",
                  tx_rate_info->mcs[0],
                  tx_rate_info->mcs[1],
                  tx_rate_info->mcs[2],
                  tx_rate_info->mcs[3],
                  tx_rate_info->mcs[4],
                  tx_rate_info->mcs[5],
                  tx_rate_info->mcs[6],
                  tx_rate_info->mcs[7],
                  tx_rate_info->mcs[8],
                  tx_rate_info->mcs[9]
                  );

   /* SGI */
    adf_os_print("SGI counts (0..9): ");
    adf_os_print("%d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n",
                  tx_rate_info->sgi[0],
                  tx_rate_info->sgi[1],
                  tx_rate_info->sgi[2],
                  tx_rate_info->sgi[3],
                  tx_rate_info->sgi[4],
                  tx_rate_info->sgi[5],
                  tx_rate_info->sgi[6],
                  tx_rate_info->sgi[7],
                  tx_rate_info->sgi[8],
                  tx_rate_info->sgi[9]
                  );

    /* NSS */
    adf_os_print("NSS  counts: ");
    adf_os_print("1x1 %d, 2x2 %d, 3x3 %d\n",
                  tx_rate_info->nss[0],
                  tx_rate_info->nss[1],
                  tx_rate_info->nss[2]
                  );

    /* BW */
    adf_os_print("BW counts: ");
    adf_os_print("20MHz %d, 40MHz %d, 80MHz %d\n",
                  tx_rate_info->bw[0],
                  tx_rate_info->bw[1],
                  tx_rate_info->bw[2]
                  );

     /* Preamble */
    adf_os_print("Preamble (O C H V) counts: ");
    adf_os_print("%d, %d, %d, %d\n",
                  tx_rate_info->pream[0],
                  tx_rate_info->pream[1],
                  tx_rate_info->pream[2],
                  tx_rate_info->pream[3]
                  );

     /* STBC rate counts */
    adf_os_print("STBC rate counts (0..9): ");
    adf_os_print("%d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n",
                  tx_rate_info->stbc[0],
                  tx_rate_info->stbc[1],
                  tx_rate_info->stbc[2],
                  tx_rate_info->stbc[3],
                  tx_rate_info->stbc[4],
                  tx_rate_info->stbc[5],
                  tx_rate_info->stbc[6],
                  tx_rate_info->stbc[7],
                  tx_rate_info->stbc[8],
                  tx_rate_info->stbc[9]
                  );

     /* LDPC and TxBF counts */
    adf_os_print("LDPC Counts: ");
    adf_os_print("%d\n", tx_rate_info->ldpc);
    adf_os_print("RTS Counts: ");
    adf_os_print("%d\n", tx_rate_info->rts_cnt);
    /* RSSI Values for last ack frames */
    adf_os_print("Ack RSSI: %d\n",tx_rate_info->ack_rssi);
}

static void htt_t2h_stats_rx_rate_stats_print(
    wlan_dbg_rx_rate_info_t *rx_phy_info, int concise)
{
    adf_os_print("RX Rate Info:\n");

    /* MCS */
    adf_os_print("MCS counts (0..9): ");
    adf_os_print("%d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n",
                  rx_phy_info->mcs[0],
                  rx_phy_info->mcs[1],
                  rx_phy_info->mcs[2],
                  rx_phy_info->mcs[3],
                  rx_phy_info->mcs[4],
                  rx_phy_info->mcs[5],
                  rx_phy_info->mcs[6],
                  rx_phy_info->mcs[7],
                  rx_phy_info->mcs[8],
                  rx_phy_info->mcs[9]
                  );

   /* SGI */
    adf_os_print("SGI counts (0..9): ");
    adf_os_print("%d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n",
                  rx_phy_info->sgi[0],
                  rx_phy_info->sgi[1],
                  rx_phy_info->sgi[2],
                  rx_phy_info->sgi[3],
                  rx_phy_info->sgi[4],
                  rx_phy_info->sgi[5],
                  rx_phy_info->sgi[6],
                  rx_phy_info->sgi[7],
                  rx_phy_info->sgi[8],
                  rx_phy_info->sgi[9]
                  );

    /* NSS */
    adf_os_print("NSS  counts: ");
    /* nss[0] just holds the count of non-stbc frames that were sent at 1x1
     * rates and nsts holds the count of frames sent with stbc. It was decided
     * to not include PPDUs sent w/ STBC in nss[0] since it would be easier to
     * change the value that needs to be printed (from "stbc+non-stbc count to
     * only non-stbc count") if needed in the future. Hence the addition in the
     * host code at this line. */
    adf_os_print("1x1 %d, 2x2 %d, 3x3 %d, 4x4 %d\n",
                  rx_phy_info->nss[0] + rx_phy_info->nsts,
                  rx_phy_info->nss[1],
                  rx_phy_info->nss[2],
                  rx_phy_info->nss[3]
                  );

    /* NSTS */
    adf_os_print("NSTS count: ");
    adf_os_print("%d\n", rx_phy_info->nsts);

    /* BW */
    adf_os_print("BW counts: ");
    adf_os_print("20MHz %d, 40MHz %d, 80MHz %d\n",
                  rx_phy_info->bw[0],
                  rx_phy_info->bw[1],
                  rx_phy_info->bw[2]
                  );

     /* Preamble */
    adf_os_print("Preamble counts: ");
    adf_os_print("%d, %d, %d, %d, %d, %d\n",
                  rx_phy_info->pream[0],
                  rx_phy_info->pream[1],
                  rx_phy_info->pream[2],
                  rx_phy_info->pream[3],
                  rx_phy_info->pream[4],
                  rx_phy_info->pream[5]
                  );

     /* STBC rate counts */
    adf_os_print("STBC rate counts (0..9): ");
    adf_os_print("%d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n",
                  rx_phy_info->stbc[0],
                  rx_phy_info->stbc[1],
                  rx_phy_info->stbc[2],
                  rx_phy_info->stbc[3],
                  rx_phy_info->stbc[4],
                  rx_phy_info->stbc[5],
                  rx_phy_info->stbc[6],
                  rx_phy_info->stbc[7],
                  rx_phy_info->stbc[8],
                  rx_phy_info->stbc[9]
                  );

     /* LDPC and TxBF counts */
    adf_os_print("LDPC TXBF Counts: ");
    adf_os_print("%d, %d\n", rx_phy_info->ldpc, rx_phy_info->txbf);
    /* RSSI Values for last received frames */
    adf_os_print("RSSI (data, mgmt): %d, %d\n",rx_phy_info->data_rssi,
                    rx_phy_info->mgmt_rssi);

    adf_os_print("RSSI Chain 0 (0x%02x 0x%02x 0x%02x 0x%02x)\n",
        ((rx_phy_info->rssi_chain0 >> 24) & 0xff),
        ((rx_phy_info->rssi_chain0 >> 16) & 0xff),
        ((rx_phy_info->rssi_chain0 >> 8) & 0xff),
        ((rx_phy_info->rssi_chain0 >> 0) & 0xff));

    adf_os_print("RSSI Chain 1 (0x%02x 0x%02x 0x%02x 0x%02x)\n",
        ((rx_phy_info->rssi_chain1 >> 24) & 0xff),
        ((rx_phy_info->rssi_chain1 >> 16) & 0xff),
        ((rx_phy_info->rssi_chain1 >> 8) & 0xff),
        ((rx_phy_info->rssi_chain1 >> 0) & 0xff));

    adf_os_print("RSSI Chain 2 (0x%02x 0x%02x 0x%02x 0x%02x)\n",
        ((rx_phy_info->rssi_chain2 >> 24) & 0xff),
        ((rx_phy_info->rssi_chain2 >> 16) & 0xff),
        ((rx_phy_info->rssi_chain2 >> 8) & 0xff),
        ((rx_phy_info->rssi_chain2 >> 0) & 0xff));
}


static void
htt_t2h_stats_pdev_stats_print(
    struct wlan_dbg_stats *wlan_pdev_stats, int concise)
{
    struct wlan_dbg_tx_stats *tx = &wlan_pdev_stats->tx;
    struct wlan_dbg_rx_stats *rx = &wlan_pdev_stats->rx;

    adf_os_print("WAL Pdev stats:\n");
    adf_os_print("\n### Tx ###\n");

    /* Num HTT cookies queued to dispatch list */
	adf_os_print("comp_queued       :\t%d\n",tx->comp_queued);
    /* Num HTT cookies dispatched */
	adf_os_print("comp_delivered    :\t%d\n",tx->comp_delivered);
    /* Num MSDU queued to WAL */
	adf_os_print("msdu_enqued       :\t%d\n",tx->msdu_enqued);
    /* Num MPDU queued to WAL */
	adf_os_print("mpdu_enqued       :\t%d\n",tx->mpdu_enqued);
    /* Num MSDUs dropped by WMM limit */
	adf_os_print("wmm_drop          :\t%d\n",tx->wmm_drop);
    /* Num Local frames queued */
	adf_os_print("local_enqued      :\t%d\n",tx->local_enqued);
    /* Num Local frames done */
	adf_os_print("local_freed       :\t%d\n",tx->local_freed);
    /* Num queued to HW */
	adf_os_print("hw_queued         :\t%d\n",tx->hw_queued);
    /* Num PPDU reaped from HW */
	adf_os_print("hw_reaped         :\t%d\n",tx->hw_reaped);
    /* Num underruns */
	adf_os_print("mac underrun      :\t%d\n",tx->underrun);
    /* Num underruns */
	adf_os_print("phy underrun      :\t%d\n",tx->phy_underrun);
    /* Num PPDUs cleaned up in TX abort */
    adf_os_print("tx_abort          :\t%d\n",tx->tx_abort);
    /* Num MPDUs requed by SW */
    adf_os_print("mpdus_requed      :\t%d\n",tx->mpdus_requed);
    /* Excessive retries */
    adf_os_print("excess retries    :\t%d\n",tx->tx_ko);
    /* last data rate */
    adf_os_print("last rc           :\t%d\n",tx->data_rc);
    /* scheduler self triggers */
    adf_os_print("sched self trig   :\t%d\n",tx->self_triggers);
    /* SW retry failures */
    adf_os_print("ampdu retry failed:\t%d\n",tx->sw_retry_failure);
    /* ilegal phy rate errirs */
    adf_os_print("illegal rate errs :\t%d\n",tx->illgl_rate_phy_err);
    /* pdev continous excessive retries  */
    adf_os_print("pdev cont xretry  :\t%d\n",tx->pdev_cont_xretry);
    /* pdev continous excessive retries  */
    adf_os_print("pdev tx timeout   :\t%d\n",tx->pdev_tx_timeout);
    /* pdev resets  */
    adf_os_print("pdev resets       :\t%d\n",tx->pdev_resets);
    /* PPDU > txop duration  */
    adf_os_print("ppdu txop ovf     :\t%d\n",tx->txop_ovf);

    adf_os_print("\n### Rx ###\n");
    /* Cnts any change in ring routing mid-ppdu */
	adf_os_print("ppdu_route_change :\t%d\n",rx->mid_ppdu_route_change);
    /* Total number of statuses processed */
	adf_os_print("status_rcvd       :\t%d\n",rx->status_rcvd);
    /* Extra frags on rings 0-3 */
	adf_os_print("r0_frags          :\t%d\n",rx->r0_frags);
	adf_os_print("r1_frags          :\t%d\n",rx->r1_frags);
	adf_os_print("r2_frags          :\t%d\n",rx->r2_frags);
	adf_os_print("r3_frags          :\t%d\n",rx->r3_frags);
    /* MSDUs / MPDUs delivered to HTT */
	adf_os_print("htt_msdus         :\t%d\n",rx->htt_msdus);
	adf_os_print("htt_mpdus         :\t%d\n",rx->htt_mpdus);
    /* MSDUs / MPDUs delivered to local stack */
	adf_os_print("loc_msdus         :\t%d\n",rx->loc_msdus);
	adf_os_print("loc_mpdus         :\t%d\n",rx->loc_mpdus);
    /* AMSDUs that have more MSDUs than the status ring size */
	adf_os_print("oversize_amsdu    :\t%d\n",rx->oversize_amsdu);
    /* Number of PHY errors */
	adf_os_print("phy_errs          :\t%d\n",rx->phy_errs);
    /* Number of PHY errors dropped */
	adf_os_print("phy_errs dropped  :\t%d\n",rx->phy_err_drop);
    /* Number of mpdu errors - FCS, MIC, ENC etc. */
	adf_os_print("mpdu_errs         :\t%d\n",rx->mpdu_errs);

}

static void
htt_t2h_stats_rx_reorder_stats_print(
    struct rx_reorder_stats *stats_ptr, int concise)
{
    adf_os_print("Rx reorder statistics:\n");
    adf_os_print("  %u non-QoS frames received\n",
                 stats_ptr->deliver_non_qos);
    adf_os_print("  %u frames received in-order\n",
                 stats_ptr->deliver_in_order);
    adf_os_print("  %u frames flushed due to timeout\n",
                 stats_ptr->deliver_flush_timeout);
    adf_os_print("  %u frames flushed due to moving out of window\n",
                 stats_ptr->deliver_flush_oow);
    adf_os_print("  %u frames flushed due to receiving DELBA\n",
                 stats_ptr->deliver_flush_delba);
    adf_os_print("  %u frames discarded due to FCS error\n",
                 stats_ptr->fcs_error);
    adf_os_print("  %u frames discarded due to invalid peer\n",
                 stats_ptr->invalid_peer);
    adf_os_print("  %u frames discarded due to duplication (non aggregation)\n",
                 stats_ptr->dup_non_aggr);
    adf_os_print("  %u frames discarded due to duplication in "
                 "reorder queue\n", stats_ptr->dup_in_reorder);
    adf_os_print("  %u frames discarded due to processed before\n",
                 stats_ptr->dup_past);
    adf_os_print("  %u times reorder timeout happened\n",
                 stats_ptr->reorder_timeout);
    adf_os_print("  %u times incorrect bar received\n",
                 stats_ptr->invalid_bar_ssn);
    adf_os_print("  %u times bar ssn reset happened\n",
                 stats_ptr->ssn_reset);
    adf_os_print("  %u times flushed due to peer delete\n",
                 stats_ptr->deliver_flush_delpeer);
    adf_os_print("  %u times flushed due to offload\n",
                 stats_ptr->deliver_flush_offload);
    adf_os_print("  %u times flushed due to ouf of buffer\n",
                 stats_ptr->deliver_flush_oob);
    adf_os_print("  %u MPDU's dropped due to PN check fail\n",
                 stats_ptr->pn_fail);
    adf_os_print("  %u MPDU's dropped due to lack of memory\n",
                 stats_ptr->store_fail);
    adf_os_print("  %u times tid pool alloc succeeded\n",
                 stats_ptr->tid_pool_alloc_succ);
    adf_os_print("  %u times MPDU pool alloc succeeded\n",
                 stats_ptr->mpdu_pool_alloc_succ);
    adf_os_print("  %u times MSDU pool alloc succeeded\n",
                 stats_ptr->msdu_pool_alloc_succ);
    adf_os_print("  %u times tid pool alloc failed\n",
                 stats_ptr->tid_pool_alloc_fail);
    adf_os_print("  %u times MPDU pool alloc failed\n",
                 stats_ptr->mpdu_pool_alloc_fail);
    adf_os_print("  %u times MSDU pool alloc failed\n",
                 stats_ptr->msdu_pool_alloc_fail);
    adf_os_print("  %u times tid pool freed\n",
                 stats_ptr->tid_pool_free);
    adf_os_print("  %u times MPDU pool freed\n",
                 stats_ptr->mpdu_pool_free);
    adf_os_print("  %u times MSDU pool freed\n",
                 stats_ptr->msdu_pool_free);
    adf_os_print("  %u MSDUs undelivered to HTT, queued to Rx MSDU free list\n",
                 stats_ptr->msdu_queued);
    adf_os_print("  %u MSDUs released from Rx MSDU list to MAC ring\n",
                 stats_ptr->msdu_recycled);
    adf_os_print("  %u MPDUs with invalid peer but A2 found in AST\n",
                 stats_ptr->invalid_peer_a2_in_ast);
    adf_os_print("  %u MPDUs with invalid peer but A3 found in AST\n",
                 stats_ptr->invalid_peer_a3_in_ast);
    adf_os_print("  %u MPDUs with invalid peer, Broadcast or Mulitcast frame\n",
                 stats_ptr->invalid_peer_bmc_mpdus);
    adf_os_print("  %u MSDUs with err attention word\n",
                 stats_ptr->rxdesc_err_att);
    adf_os_print("  %u MSDUs with flag of peer_idx_invalid\n",
                 stats_ptr->rxdesc_err_peer_idx_inv);
    adf_os_print("  %u MSDUs with  flag of peer_idx_timeout\n",
                 stats_ptr->rxdesc_err_peer_idx_to);
    adf_os_print("  %u MSDUs with  flag of overflow\n",
                 stats_ptr->rxdesc_err_ov);
    adf_os_print("  %u MSDUs with  flag of msdu_length_err\n",
                 stats_ptr->rxdesc_err_msdu_len);
    adf_os_print("  %u MSDUs with  flag of mpdu_length_err\n",
                 stats_ptr->rxdesc_err_mpdu_len);
    adf_os_print("  %u MSDUs with  flag of tkip_mic_err\n",
                 stats_ptr->rxdesc_err_tkip_mic);
    adf_os_print("  %u MSDUs with  flag of decrypt_err\n",
                 stats_ptr->rxdesc_err_decrypt);
    adf_os_print("  %u MSDUs with  flag of fcs_err\n",
                 stats_ptr->rxdesc_err_fcs);
    adf_os_print("  %u Unicast frames with invalid peer handler\n",
                 stats_ptr->rxdesc_uc_msdus_inv_peer);
    adf_os_print("  %u unicast frame directly to DUT with invalid peer handler\n",
                 stats_ptr->rxdesc_direct_msdus_inv_peer);
    adf_os_print("  %u Broadcast/Multicast frames with invalid peer handler\n",
                 stats_ptr->rxdesc_bmc_msdus_inv_peer);
    adf_os_print("  %u MSDUs dropped due to no first MSDU flag\n",
                 stats_ptr->rxdesc_no_1st_msdu);
    adf_os_print("  %u MSDUs dropped due to ring overflow\n",
                 stats_ptr->msdu_drop_ring_ov);
    adf_os_print("  %u MSDUs dropped due to FC mismatch\n",
                 stats_ptr->msdu_drop_fc_mismatch);
    adf_os_print("  %u MSDUs dropped due to mgt frame in Remote ring\n",
                 stats_ptr->msdu_drop_mgmt_remote_ring);
    adf_os_print("  %u MSDUs dropped due to misc non error\n",
                 stats_ptr->msdu_drop_misc);
    adf_os_print("  %u MSDUs go to offload before reorder\n",
                 stats_ptr->offload_msdu_wal);
    adf_os_print("  %u data frame dropped by offload after reorder\n",
                 stats_ptr->offload_msdu_reorder);
    adf_os_print("  %u  MPDUs with SN in the past & within BA window\n",
                 stats_ptr->dup_past_within_window);
    adf_os_print("  %u  MPDUs with SN in the past & outside BA window\n",
                 stats_ptr->dup_past_outside_window);
}

static void
htt_t2h_stats_rx_rem_buf_stats_print(
    struct rx_remote_buffer_mgmt_stats *stats_ptr, int concise)
{
    adf_os_print("Rx Remote Buffer Statistics:\n");
    adf_os_print("  %u MSDU's reaped for Rx processing\n",
                 stats_ptr->remote_reaped);
    adf_os_print("  %u MSDU's recycled within firmware\n",
                 stats_ptr->remote_recycled);
    adf_os_print("  %u MSDU's stored by Data Rx\n",
                 stats_ptr->data_rx_msdus_stored);
    adf_os_print("  %u HTT indications from WAL Rx MSDU\n",
                 stats_ptr->wal_rx_ind);
    adf_os_print("  %u HTT indications unconsumed from WAL Rx MSDU\n",
                 stats_ptr->wal_rx_ind_unconsumed);
    adf_os_print("  %u HTT indications from Data Rx MSDU\n",
                 stats_ptr->data_rx_ind);
    adf_os_print("  %u HTT indications unconsumed from Data Rx MSDU\n",
                 stats_ptr->data_rx_ind_unconsumed);
    adf_os_print("  %u HTT indications from ATHBUF\n",
                 stats_ptr->athbuf_rx_ind);
    adf_os_print("  %u Remote buffers requested for refill\n",
                 stats_ptr->refill_buf_req);
    adf_os_print("  %u Remote buffers filled by host\n",
                 stats_ptr->refill_buf_rsp);
    adf_os_print("  %u times MAC has no buffers\n",
                 stats_ptr->mac_no_bufs);
    adf_os_print("  %u times f/w write & read indices on MAC ring are equal\n",
                 stats_ptr->fw_indices_equal);
    adf_os_print("  %u times f/w has no remote buffers to post to MAC\n",
                 stats_ptr->host_no_bufs);
}

static void
htt_t2h_rx_musu_ndpa_pkts_stats_print(
    struct rx_txbf_musu_ndpa_pkts_stats *stats_ptr, int concise)
{
    adf_os_print("Rx TXBF MU/SU Packets and NDPA Statistics:\n");
    adf_os_print("  %u Number of TXBF MU packets received\n",
                 stats_ptr->number_mu_pkts);
    adf_os_print("  %u Number of TXBF SU packets received\n",
                 stats_ptr->number_su_pkts);
    adf_os_print("  %u Number of TXBF directed NDPA\n",
                 stats_ptr->txbf_directed_ndpa_count);
    adf_os_print("  %u Number of TXBF retried NDPA\n",
                 stats_ptr->txbf_ndpa_retry_count);
    adf_os_print("  %u Total number of TXBF NDPA\n",
                 stats_ptr->txbf_total_ndpa_count);
}

#define HTT_T2H_STATS_TX_PPDU_TIME_TO_MICROSEC(ticks, microsec_per_tick) \
    (ticks * microsec_per_tick)
static inline int
HTT_T2H_STATS_TX_PPDU_RATE_FLAGS_TO_MHZ(u_int8_t rate_flags)
{
    if (rate_flags & 0x20) return 40;   /* WHAL_RC_FLAG_40MHZ */
    if (rate_flags & 0x40) return 80;   /* WHAL_RC_FLAG_80MHZ */
    if (rate_flags & 0x80) return 160;  /* WHAL_RC_FLAG_160MHZ */
    return 20;
}

#define HTT_FW_STATS_MAX_BLOCK_ACK_WINDOW 64

static void
htt_t2h_tx_ppdu_log_bitmaps_print(
    u_int32_t *queued_ptr,
    u_int32_t *acked_ptr)
{
    char queued_str[HTT_FW_STATS_MAX_BLOCK_ACK_WINDOW+1];
    char acked_str[HTT_FW_STATS_MAX_BLOCK_ACK_WINDOW+1];
    int i, j, word;

    adf_os_mem_set(queued_str, '0', HTT_FW_STATS_MAX_BLOCK_ACK_WINDOW);
    adf_os_mem_set(acked_str, '-', HTT_FW_STATS_MAX_BLOCK_ACK_WINDOW);
    i = 0;
    for (word = 0; word < 2; word++) {
        u_int32_t queued = *(queued_ptr + word);
        u_int32_t acked = *(acked_ptr + word);
        for (j = 0; j < 32; j++, i++) {
            if (queued & (1 << j)) {
                queued_str[i] = '1';
                acked_str[i] = (acked & (1 << j)) ? 'y' : 'N';
            }
        }
    }
    queued_str[HTT_FW_STATS_MAX_BLOCK_ACK_WINDOW] = '\0';
    acked_str[HTT_FW_STATS_MAX_BLOCK_ACK_WINDOW] = '\0';
    adf_os_print("%s\n", queued_str);
    adf_os_print("%s\n", acked_str);
}

static inline u_int16_t htt_msg_read16(u_int16_t *p16)
{
#ifdef BIG_ENDIAN_HOST
    /*
     * During upload, the bytes within each u_int32_t word were
     * swapped by the HIF HW.  This results in the lower and upper bytes
     * of each u_int16_t to be in the correct big-endian order with
     * respect to each other, but for each even-index u_int16_t to
     * have its position switched with its successor neighbor u_int16_t.
     * Undo this u_int16_t position swapping.
     */
    return (((size_t) p16) & 0x2) ? *(p16 - 1) : *(p16 + 1);
#else
    return *p16;
#endif
}

static inline u_int8_t htt_msg_read8(u_int8_t *p8)
{
#ifdef BIG_ENDIAN_HOST
    /*
     * During upload, the bytes within each u_int32_t word were
     * swapped by the HIF HW.
     * Undo this byte swapping.
     */
    switch (((size_t) p8) & 0x3) {
    case 0:
        return *(p8 + 3);
    case 1:
        return *(p8 + 1);
    case 2:
        return *(p8 - 1);
    default /* 3 */:
        return *(p8 - 3);
    }
#else
    return *p8;
#endif
}

void htt_make_u8_list_str(
    u_int32_t *aligned_data,
    char *buffer,
    int space,
    int max_elems)
{
    u_int8_t *p8 = (u_int8_t *) aligned_data;
    char *buf_p = buffer;
    while (max_elems-- > 0) {
        int bytes;
        u_int8_t val;

        val = htt_msg_read8(p8);
        if (val == 0) {
            break; /* not enough data to fill the reserved msg buffer space */
        }
        bytes = adf_os_snprint(buf_p, space, "%d,", val);
        space -= bytes;
        if (space > 0) {
            buf_p += bytes;
        } else {
            break; /* not enough print buffer space for all the data */
        }
        p8++;
    }
    if (buf_p == buffer) {
        *buf_p = '\0'; /* nothing was written */
    } else {
        *(buf_p - 1) = '\0'; /* erase the final comma */
    }
}

void htt_make_u16_list_str(
    u_int32_t *aligned_data,
    char *buffer,
    int space,
    int max_elems)
{
    u_int16_t *p16 = (u_int16_t *) aligned_data;
    char *buf_p = buffer;
    while (max_elems-- > 0) {
        int bytes;
        u_int16_t val;

        val = htt_msg_read16(p16);
        if (val == 0) {
            break; /* not enough data to fill the reserved msg buffer space */
        }
        bytes = adf_os_snprint(buf_p, space, "%d,", val);
        space -= bytes;
        if (space > 0) {
            buf_p += bytes;
        } else {
            break; /* not enough print buffer space for all the data */
        }
        p16++;
    }
    if (buf_p == buffer) {
        *buf_p = '\0'; /* nothing was written */
    } else {
        *(buf_p - 1) = '\0'; /* erase the final comma */
    }
}

void
htt_t2h_tx_ppdu_log_print(
    struct ol_fw_tx_dbg_ppdu_msg_hdr *hdr,
    struct ol_fw_tx_dbg_ppdu_base *record,
    int length, int concise)
{
    int i;
    int record_size;
    int calculated_record_size;
    int num_records;

    record_size = sizeof(*record);
    calculated_record_size = record_size +
        hdr->mpdu_bytes_array_len * sizeof(uint16_t);
    if (calculated_record_size < record_size) {
        adf_os_print("Overflow due to record and hdr->mpdu_bytes_array_len %u\n",
            hdr->mpdu_bytes_array_len);
        return;
    }
    record_size = calculated_record_size;
    calculated_record_size += hdr->mpdu_msdus_array_len * sizeof(uint8_t);
    if (calculated_record_size < record_size) {
        adf_os_print("Overflow due to hdr->mpdu_msdus_array_len %u\n",
            hdr->mpdu_msdus_array_len);
        return;
    }
    record_size = calculated_record_size;
    calculated_record_size += hdr->msdu_bytes_array_len * sizeof(uint16_t);
    if (calculated_record_size < record_size) {
        adf_os_print("Overflow due to hdr->msdu_bytes_array_len %u\n",
            hdr->msdu_bytes_array_len);
        return;
    }
    record_size = calculated_record_size;
    num_records = (length - sizeof(*hdr)) / record_size;
    if (num_records < 0) {
        adf_os_print("Underflow due to length %d\n", length);
        return;
    }
    adf_os_print("Tx PPDU log elements:\n");

    for (i = 0; i < num_records; i++) {
        u_int16_t start_seq_num;
        u_int16_t start_pn_lsbs;
        u_int8_t  num_mpdus;
        u_int16_t peer_id;
        u_int8_t  ext_tid;
        u_int8_t  rate_code;
        u_int8_t  rate_flags;
        u_int8_t  tries;
        u_int8_t  complete;
        u_int32_t time_enqueue_us;
        u_int32_t time_completion_us;
        u_int32_t *msg_word = (u_int32_t *) record;

        /* fields used for both concise and complete printouts */
        start_seq_num =
            ((*(msg_word + OL_FW_TX_DBG_PPDU_START_SEQ_NUM_WORD)) &
            OL_FW_TX_DBG_PPDU_START_SEQ_NUM_M) >>
            OL_FW_TX_DBG_PPDU_START_SEQ_NUM_S;
        complete =
            ((*(msg_word + OL_FW_TX_DBG_PPDU_COMPLETE_WORD)) &
            OL_FW_TX_DBG_PPDU_COMPLETE_M) >>
            OL_FW_TX_DBG_PPDU_COMPLETE_S;

        /* fields used only for complete printouts */
        if (! concise) {
            #define BUF_SIZE 80
            char buf[BUF_SIZE];
            u_int8_t *p8;
            u_int8_t *calculated_p8;

            time_enqueue_us = HTT_T2H_STATS_TX_PPDU_TIME_TO_MICROSEC(
                    record->timestamp_enqueue, hdr->microsec_per_tick);
            time_completion_us = HTT_T2H_STATS_TX_PPDU_TIME_TO_MICROSEC(
                    record->timestamp_completion, hdr->microsec_per_tick);

            start_pn_lsbs =
                ((*(msg_word + OL_FW_TX_DBG_PPDU_START_PN_LSBS_WORD)) &
                OL_FW_TX_DBG_PPDU_START_PN_LSBS_M) >>
                OL_FW_TX_DBG_PPDU_START_PN_LSBS_S;
            num_mpdus =
                ((*(msg_word + OL_FW_TX_DBG_PPDU_NUM_MPDUS_WORD)) &
                OL_FW_TX_DBG_PPDU_NUM_MPDUS_M) >>
                OL_FW_TX_DBG_PPDU_NUM_MPDUS_S;
            peer_id =
                ((*(msg_word + OL_FW_TX_DBG_PPDU_PEER_ID_WORD)) &
                OL_FW_TX_DBG_PPDU_PEER_ID_M) >>
                OL_FW_TX_DBG_PPDU_PEER_ID_S;
            ext_tid =
                ((*(msg_word + OL_FW_TX_DBG_PPDU_EXT_TID_WORD)) &
                OL_FW_TX_DBG_PPDU_EXT_TID_M) >>
                OL_FW_TX_DBG_PPDU_EXT_TID_S;
            rate_code =
                ((*(msg_word + OL_FW_TX_DBG_PPDU_RATE_CODE_WORD)) &
                OL_FW_TX_DBG_PPDU_RATE_CODE_M) >>
                OL_FW_TX_DBG_PPDU_RATE_CODE_S;
            rate_flags =
                ((*(msg_word + OL_FW_TX_DBG_PPDU_RATE_FLAGS_WORD)) &
                OL_FW_TX_DBG_PPDU_RATE_FLAGS_M) >>
                OL_FW_TX_DBG_PPDU_RATE_FLAGS_S;
            tries =
                ((*(msg_word + OL_FW_TX_DBG_PPDU_TRIES_WORD)) &
                OL_FW_TX_DBG_PPDU_TRIES_M) >>
                OL_FW_TX_DBG_PPDU_TRIES_S;

            adf_os_print("  - PPDU tx to peer %d, TID %d\n", peer_id, ext_tid);
            adf_os_print("    start seq num = %u, start PN LSBs = %#04x\n",
                start_seq_num, start_pn_lsbs);
            adf_os_print("    PPDU is %d MPDUs, (unknown) MSDUs, %d bytes\n",
                num_mpdus,
                //num_msdus, /* not yet being computed in target */
                record->num_bytes);
            if (complete) {
                adf_os_print("    enqueued at %u, completed at %u (microsec)\n",
                    time_enqueue_us, time_completion_us);
                adf_os_print(
                    "    %d total tries, last tx used rate %d "
                    "on %d MHz chan (flags = %#x)\n",
                    tries, rate_code,
                    HTT_T2H_STATS_TX_PPDU_RATE_FLAGS_TO_MHZ(rate_flags),
                    rate_flags);
                adf_os_print("    enqueued and acked MPDU bitmaps:\n");
                htt_t2h_tx_ppdu_log_bitmaps_print(
                    msg_word + OL_FW_TX_DBG_PPDU_ENQUEUED_LSBS_WORD,
                    msg_word + OL_FW_TX_DBG_PPDU_BLOCK_ACK_LSBS_WORD);
            } else {
                adf_os_print(
                    "    enqueued at %d ms (microsec), not yet completed\n",
                    time_enqueue_us);
            }
            /* skip past the regular message fields to reach the tail area */
            p8 = (u_int8_t *) record;
            calculated_p8 = p8 + sizeof(struct ol_fw_tx_dbg_ppdu_base);
            if (calculated_p8 < p8) {
                adf_os_print("Overflow due to record %p\n", p8);
                continue;
            }
            p8 = calculated_p8;
            if (hdr->mpdu_bytes_array_len) {
                htt_make_u16_list_str(
                    (u_int32_t *) p8, buf, BUF_SIZE, hdr->mpdu_bytes_array_len);
                adf_os_print("    MPDU bytes: %s\n", buf);
            }
            calculated_p8 += hdr->mpdu_bytes_array_len * sizeof(u_int16_t);
            if (calculated_p8 < p8) {
                adf_os_print("Overflow due to hdr->mpdu_bytes_array_len %u\n",
                    hdr->mpdu_bytes_array_len);
                continue;
            }
            p8 = calculated_p8;
            if (hdr->mpdu_msdus_array_len) {
                htt_make_u8_list_str(
                    (u_int32_t *) p8, buf, BUF_SIZE, hdr->mpdu_msdus_array_len);
                adf_os_print("    MPDU MSDUs: %s\n", buf);
            }
            calculated_p8 += hdr->mpdu_msdus_array_len * sizeof(u_int8_t);
            if (calculated_p8 < p8) {
                adf_os_print("Overflow due to hdr->mpdu_msdus_array_len %u\n",
                    hdr->mpdu_msdus_array_len);
                continue;
            }
            p8 = calculated_p8;
            if (hdr->msdu_bytes_array_len) {
                htt_make_u16_list_str(
                    (u_int32_t *) p8, buf, BUF_SIZE, hdr->msdu_bytes_array_len);
                adf_os_print("    MSDU bytes: %s\n", buf);
            }
        } else {
            /* concise */
            adf_os_print(
                "start seq num = %u, enqueued and acked MPDU bitmaps:\n",
                start_seq_num);
            if (complete) {
                htt_t2h_tx_ppdu_log_bitmaps_print(
                    msg_word + OL_FW_TX_DBG_PPDU_ENQUEUED_LSBS_WORD,
                    msg_word + OL_FW_TX_DBG_PPDU_BLOCK_ACK_LSBS_WORD);
            } else {
                adf_os_print("(not completed)\n");
            }
        }
        record = (struct ol_fw_tx_dbg_ppdu_base *)
            (((u_int8_t *) record) + record_size);
    }
}

void
htt_t2h_stats_print(u_int8_t *stats_data, int concise)
{
    u_int32_t *msg_word = (u_int32_t *)stats_data;
    enum htt_dbg_stats_type   type;
    enum htt_dbg_stats_status status;
    int length;

    type = HTT_T2H_STATS_CONF_TLV_TYPE_GET(*msg_word);
    status = HTT_T2H_STATS_CONF_TLV_STATUS_GET(*msg_word);
    length = HTT_T2H_STATS_CONF_TLV_LENGTH_GET(*msg_word);

    /* check that we've been given a valid stats type */
    if (status == HTT_DBG_STATS_STATUS_SERIES_DONE) {
        return;
    } else if (status == HTT_DBG_STATS_STATUS_INVALID) {
        adf_os_print(
            "Target doesn't support stats type %d\n", type);
        return;
    } else if (status == HTT_DBG_STATS_STATUS_ERROR) {
        adf_os_print(
            "Target couldn't upload stats type %d (no mem?)\n", type);
        return;
    }
    /* got valid (though perhaps partial) stats - process them */
    switch (type) {
    case HTT_DBG_STATS_WAL_PDEV_TXRX:
        {
            struct wlan_dbg_stats *wlan_dbg_stats_ptr;

            wlan_dbg_stats_ptr = (struct wlan_dbg_stats *)(msg_word + 1);
            htt_t2h_stats_pdev_stats_print(wlan_dbg_stats_ptr, concise);
            break;
        }
    case HTT_DBG_STATS_RX_REORDER:
        {
            struct rx_reorder_stats *rx_reorder_stats_ptr;

            rx_reorder_stats_ptr = (struct rx_reorder_stats *)(msg_word + 1);
            htt_t2h_stats_rx_reorder_stats_print(rx_reorder_stats_ptr, concise);
            break;
        }

    case HTT_DBG_STATS_RX_RATE_INFO:
        {
            wlan_dbg_rx_rate_info_t *rx_phy_info;
            rx_phy_info = (wlan_dbg_rx_rate_info_t *)(msg_word + 1);
            htt_t2h_stats_rx_rate_stats_print(rx_phy_info, concise);
            break;
        }
    case HTT_DBG_STATS_TX_PPDU_LOG:
        {
            struct ol_fw_tx_dbg_ppdu_msg_hdr *hdr;
            struct ol_fw_tx_dbg_ppdu_base *record;

            if (status == HTT_DBG_STATS_STATUS_PARTIAL && length == 0) {
               adf_os_print("HTT_DBG_STATS_TX_PPDU_LOG -- length = 0!\n");
               break;
            }
            hdr = (struct ol_fw_tx_dbg_ppdu_msg_hdr *)(msg_word + 1);
            record = (struct ol_fw_tx_dbg_ppdu_base *)(hdr + 1);
            htt_t2h_tx_ppdu_log_print(hdr, record, length, concise);
        }
        break;
    case HTT_DBG_STATS_TX_RATE_INFO:
        {
            wlan_dbg_tx_rate_info_t *tx_rate_info;
            tx_rate_info = (wlan_dbg_tx_rate_info_t *)(msg_word + 1);
            htt_t2h_stats_tx_rate_stats_print(tx_rate_info, concise);
            break;
        }
    case HTT_DBG_STATS_RX_REMOTE_RING_BUFFER_INFO:
        {

            struct rx_remote_buffer_mgmt_stats *rx_rem_buf;

            rx_rem_buf = (struct rx_remote_buffer_mgmt_stats *)(msg_word + 1);
            htt_t2h_stats_rx_rem_buf_stats_print(rx_rem_buf, concise);
            break;
        }
    case HTT_DBG_STATS_TXBF_MUSU_NDPA_PKT:
        {
            struct rx_txbf_musu_ndpa_pkts_stats *rx_musu_ndpa_stats;

            rx_musu_ndpa_stats = (struct rx_txbf_musu_ndpa_pkts_stats *)(msg_word + 1);
            htt_t2h_rx_musu_ndpa_pkts_stats_print(rx_musu_ndpa_stats, concise);
            break;
        }
    default:
        break;
    }
}
