@@ -6475,12 +6475,267 @@ int iwl_mvm_set_hw_timestamp(struct ieee80211_hw *hw,
return ret;
}
+static const char iwl_mvm_gstrings_stats[][ETH_GSTRING_LEN] = {
+ "tx_pkts_nic", /* from driver, phy tx-ok skb */
+ "tx_bytes_nic", /* from driver, phy tx-ok bytes */
+ "rx_pkts_nic", /* from driver, phy rx OK skb */
+ "rx_bytes_nic", /* from driver, phy rx OK bytes */
+
+ "tx_mpdu_attempts", /* counting any retries */
+ "tx_mpdu_fail", /* frames that failed even after retry */
+ "tx_mpdu_retry", /* number of times frames were retried */
+
+ "tx_direct_done",
+ "tx_postpone_delay",
+ "tx_postpone_few_bytes",
+ "tx_postpone_bt_prio",
+ "tx_postpone_quiet_period",
+ "tx_postpone_calc_ttak",
+ "tx_fail_internal_x_retry",
+ "tx_fail_short_limit",
+ "tx_fail_long_limit",
+ "tx_fail_underrun",
+ "tx_fail_drain_flow",
+ "tx_fail_rfkill_flush",
+ "tx_fail_life_expire",
+ "tx_fail_dest_ps",
+ "tx_fail_host_aborted",
+ "tx_fail_bt_retry",
+ "tx_fail_sta_invalid",
+ "tx_fail_frag_dropped",
+ "tx_fail_tid_disable",
+ "tx_fail_fifo_flushed",
+ "tx_fail_small_cf_poll",
+ "tx_fail_fw_drop",
+ "tx_fail_color_mismatch",
+ "tx_fail_internal_abort",
+ "tx_fail_unknown_oor",
+
+ "tx_mode_cck",
+ "tx_mode_ofdm",
+ "tx_mode_ht",
+ "tx_mode_vht",
+ "tx_mode_he",
+ "tx_mode_eht",
+ "tx_mode_he_su",
+ "tx_mode_he_ext_su",
+ "tx_mode_he_mu",
+ "tx_mode_he_trig",
+
+ "tx_ampdu_len:0-1",
+ "tx_ampdu_len:2-10",
+ "tx_ampdu_len:11-19",
+ "tx_ampdu_len:20-28",
+ "tx_ampdu_len:29-37",
+ "tx_ampdu_len:38-46",
+ "tx_ampdu_len:47-55",
+ "tx_ampdu_len:56-79",
+ "tx_ampdu_len:80-103",
+ "tx_ampdu_len:104-127",
+ "tx_ampdu_len:128-151",
+ "tx_ampdu_len:152-175",
+ "tx_ampdu_len:176-199",
+ "tx_ampdu_len:200-223",
+ "tx_ampdu_len:224-247", /* and higher */
+
+ "tx_bw_20",
+ "tx_bw_40",
+ "tx_bw_80",
+ "tx_bw_160",
+ "tx_bw_320",
+ "tx_bw_106_tone",
+
+ "tx_mcs_0",
+ "tx_mcs_1",
+ "tx_mcs_2",
+ "tx_mcs_3",
+ "tx_mcs_4",
+ "tx_mcs_5",
+ "tx_mcs_6",
+ "tx_mcs_7",
+ "tx_mcs_8",
+ "tx_mcs_9",
+ "tx_mcs_10",
+ "tx_mcs_11",
+ "tx_mcs_12",
+ "tx_mcs_13",
+
+ "tx_nss_1",
+ "tx_nss_2",
+
+ /* rx stats */
+ "rx_crc_err",
+ "rx_fifo_underrun",
+ "rx_failed_decrypt",
+ "rx_dup",
+ "rx_bad_header_len",
+
+ "rx_mode_cck",
+ "rx_mode_ofdm",
+ "rx_mode_ht",
+ "rx_mode_vht",
+ "rx_mode_he",
+ "rx_mode_eht",
+
+ "rx_mode_he_su",
+ "rx_mode_he_ext_su",
+ "rx_mode_he_mu",
+ "rx_mode_he_trig",
+
+ "rx_bw_20",
+ "rx_bw_40",
+ "rx_bw_80",
+ "rx_bw_160",
+ "rx_bw_320",
+ "rx_bw_he_ru",
+
+ "rx_mcs_0",
+ "rx_mcs_1",
+ "rx_mcs_2",
+ "rx_mcs_3",
+ "rx_mcs_4",
+ "rx_mcs_5",
+ "rx_mcs_6",
+ "rx_mcs_7",
+ "rx_mcs_8",
+ "rx_mcs_9",
+ "rx_mcs_10",
+ "rx_mcs_11",
+ "rx_mcs_12",
+ "rx_mcs_13",
+
+ "rx_nss_1",
+ "rx_nss_2",
+};
+
+#define IWL_MVM_SSTATS_LEN ARRAY_SIZE(iwl_mvm_gstrings_stats)
+
+void iwl_mvm_get_et_strings(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ u32 sset, u8 *data)
+{
+ if (sset != ETH_SS_STATS)
+ return;
+
+ memcpy(data, *iwl_mvm_gstrings_stats, sizeof(iwl_mvm_gstrings_stats));
+}
+
+int iwl_mvm_get_et_sset_count(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, int sset)
+{
+ if (sset != ETH_SS_STATS)
+ return 0;
+
+ return IWL_MVM_SSTATS_LEN;
+}
+
+void iwl_mvm_get_et_stats(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
+
+ int i, ei = 0;
+
+ /* driver phy-wide stats */
+ struct iwl_mvm_ethtool_stats *mib = &mvm->ethtool_stats;
+
+ data[ei++] = mib->tx_status_counts[TX_STATUS_SUCCESS];
+ data[ei++] = mib->tx_bytes_nic;
+ data[ei++] = mib->rx_pkts;
+ data[ei++] = mib->rx_bytes_nic;
+
+ data[ei++] = mib->tx_mpdu_attempts;
+ data[ei++] = mib->tx_mpdu_fail;
+ data[ei++] = mib->tx_mpdu_retry;
+
+ data[ei++] = mib->tx_status_counts[TX_STATUS_DIRECT_DONE];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_POSTPONE_DELAY];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_POSTPONE_FEW_BYTES];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_POSTPONE_BT_PRIO];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_POSTPONE_QUIET_PERIOD];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_POSTPONE_CALC_TTAK];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_SHORT_LIMIT];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_LONG_LIMIT];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_UNDERRUN];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_DRAIN_FLOW];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_RFKILL_FLUSH];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_LIFE_EXPIRE];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_DEST_PS];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_HOST_ABORTED];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_BT_RETRY];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_STA_INVALID];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_FRAG_DROPPED];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_TID_DISABLE];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_FIFO_FLUSHED];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_SMALL_CF_POLL];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_FW_DROP];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_FAIL_STA_COLOR_MISMATCH];
+ data[ei++] = mib->tx_status_counts[TX_STATUS_INTERNAL_ABORT];
+ /* Failed out-of-range */
+ data[ei++] = mib->tx_status_counts[TX_STATUS_INTERNAL_ABORT + 1];
+
+ data[ei++] = mib->tx_cck;
+ data[ei++] = mib->tx_ofdm;
+ data[ei++] = mib->tx_ht;
+ data[ei++] = mib->tx_vht;
+ data[ei++] = mib->tx_he;
+ data[ei++] = mib->tx_eht;
+
+ for (i = 0; i < ARRAY_SIZE(mib->tx_he_type); i++)
+ data[ei++] = mib->tx_he_type[i];
+
+ for (i = 0; i < ARRAY_SIZE(mib->tx_ampdu_len); i++)
+ data[ei++] = mib->tx_ampdu_len[i];
+
+ for (i = 0; i < ARRAY_SIZE(mib->tx_bw); i++)
+ data[ei++] = mib->tx_bw[i];
+ data[ei++] = mib->tx_bw_106_tone;
+
+ for (i = 0; i < ARRAY_SIZE(mib->tx_mcs); i++)
+ data[ei++] = mib->tx_mcs[i];
+
+ for (i = 0; i < ARRAY_SIZE(mib->tx_nss); i++)
+ data[ei++] = mib->tx_nss[i];
+
+ /* rx counters */
+ data[ei++] = mib->rx_crc_err;
+ data[ei++] = mib->rx_fifo_underrun;
+ data[ei++] = mib->rx_failed_decrypt;
+ data[ei++] = mib->rx_dup;
+ data[ei++] = mib->rx_bad_header_len;
+
+ for (i = 0; i < ARRAY_SIZE(mib->rx_mode); i++)
+ data[ei++] = mib->rx_mode[i];
+
+ for (i = 0; i < ARRAY_SIZE(mib->rx_he_type); i++)
+ data[ei++] = mib->rx_he_type[i];
+
+ for (i = 0; i < ARRAY_SIZE(mib->rx_bw); i++)
+ data[ei++] = mib->rx_bw[i];
+ data[ei++] = mib->rx_bw_he_ru;
+
+ for (i = 0; i < ARRAY_SIZE(mib->rx_mcs); i++)
+ data[ei++] = mib->rx_mcs[i];
+
+ for (i = 0; i < ARRAY_SIZE(mib->rx_nss); i++)
+ data[ei++] = mib->rx_nss[i];
+
+ if (ei != IWL_MVM_SSTATS_LEN)
+ pr_err("ERROR: iwlwifi ethtool stats bug: ei: %d size: %d",
+ ei, (int)(IWL_MVM_SSTATS_LEN));
+}
+
const struct ieee80211_ops iwl_mvm_hw_ops = {
.tx = iwl_mvm_mac_tx,
.wake_tx_queue = iwl_mvm_mac_wake_tx_queue,
.ampdu_action = iwl_mvm_mac_ampdu_action,
.get_antenna = iwl_mvm_op_get_antenna,
.set_antenna = iwl_mvm_op_set_antenna,
+ .get_et_sset_count = iwl_mvm_get_et_sset_count,
+ .get_et_stats = iwl_mvm_get_et_stats,
+ .get_et_strings = iwl_mvm_get_et_strings,
.start = iwl_mvm_mac_start,
.reconfig_complete = iwl_mvm_mac_reconfig_complete,
.stop = iwl_mvm_mac_stop,
@@ -1165,6 +1165,9 @@ const struct ieee80211_ops iwl_mvm_mld_hw_ops = {
.ampdu_action = iwl_mvm_mac_ampdu_action,
.get_antenna = iwl_mvm_op_get_antenna,
.set_antenna = iwl_mvm_op_set_antenna,
+ .get_et_sset_count = iwl_mvm_get_et_sset_count,
+ .get_et_stats = iwl_mvm_get_et_stats,
+ .get_et_strings = iwl_mvm_get_et_strings,
.start = iwl_mvm_mac_start,
.reconfig_complete = iwl_mvm_mac_reconfig_complete,
.stop = iwl_mvm_mac_stop,
@@ -572,6 +572,48 @@ struct iwl_mvm_cooling_device {
};
#endif
+struct iwl_mvm_ethtool_stats {
+ u64 tx_bytes_nic; /* successful tx bytes */
+
+ u64 tx_mpdu_attempts; /* counting any retries */
+ u64 tx_mpdu_fail; /* Failed even after retry */
+ u64 tx_mpdu_retry; /* Number of times frames were retried */
+
+ /* maps to iwl_tx_status enum
+ * (TX_STATUS_INTERNAL_ABORT + 1) gathers all larger values.
+ */
+ u64 tx_status_counts[TX_STATUS_INTERNAL_ABORT + 2];
+
+ u64 tx_cck;
+ u64 tx_ofdm;
+ u64 tx_ht;
+ u64 tx_vht;
+ u64 tx_he;
+ u64 tx_eht;
+
+ u64 tx_he_type[4]; /* su, ext_su, mu, trig */
+ u64 tx_ampdu_len[15];
+ u64 tx_bw[5]; /* 20, 40, 80, 160, 320 */
+ u64 tx_bw_106_tone;
+ u64 tx_mcs[14]; /* mcs 0 to mcs 13 */
+ u64 tx_nss[2]; /* tx nss histogram */
+
+ u64 rx_pkts; /* successful rx skb */
+ u64 rx_bytes_nic; /* successful tx bytes */
+ u64 rx_crc_err;
+ u64 rx_fifo_underrun;
+ u64 rx_failed_decrypt;
+ u64 rx_dup;
+ u64 rx_bad_header_len;
+
+ u64 rx_mode[6]; /* cck, ofdm, ht, vht, he, eht */
+ u64 rx_he_type[4]; /* su, ext_su, mu, trig */
+ u64 rx_bw[5]; /* 20, 40, 80, 160, 320 */
+ u64 rx_bw_he_ru;
+ u64 rx_mcs[14]; /* mcs 0 to mcs 13 */
+ u64 rx_nss[2]; /* rx nss histogram */
+};
+
#define IWL_MVM_NUM_LAST_FRAMES_UCODE_RATES 8
struct iwl_mvm_frame_stats {
@@ -904,6 +946,7 @@ struct iwl_mvm {
struct mvm_statistics_rx_v3 rx_stats_v3;
struct mvm_statistics_rx rx_stats;
};
+ struct iwl_mvm_ethtool_stats ethtool_stats;
struct {
u64 rx_time;
@@ -1670,7 +1713,8 @@ int iwl_mvm_legacy_hw_idx_to_mac80211_idx(u32 rate_n_flags,
enum nl80211_band band);
int iwl_mvm_legacy_rate_to_mac80211_idx(u32 rate_n_flags,
enum nl80211_band band);
-void iwl_mvm_hwrate_to_tx_rate(u32 rate_n_flags,
+void iwl_mvm_hwrate_to_tx_rate(struct iwl_mvm *mvm,
+ u32 rate_n_flags,
enum nl80211_band band,
struct ieee80211_tx_rate *r);
void iwl_mvm_hwrate_to_tx_rate_v1(u32 rate_n_flags,
@@ -2697,6 +2741,14 @@ int iwl_mvm_mac_ampdu_action(struct ieee80211_hw *hw,
struct ieee80211_ampdu_params *params);
int iwl_mvm_op_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant);
int iwl_mvm_op_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant);
+int iwl_mvm_get_et_sset_count(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif, int sset);
+void iwl_mvm_get_et_stats(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ethtool_stats *stats, u64 *data);
+void iwl_mvm_get_et_strings(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ u32 sset, u8 *data);
int iwl_mvm_mac_start(struct ieee80211_hw *hw);
void iwl_mvm_mac_reconfig_complete(struct ieee80211_hw *hw,
enum ieee80211_reconfig_type reconfig_type);
@@ -300,6 +300,7 @@ void iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct napi_struct *napi,
u32 rate_n_flags;
u32 rx_pkt_status;
u8 crypt_len = 0;
+ bool bad_pkt = false;
if (unlikely(pkt_len < sizeof(*rx_res))) {
IWL_DEBUG_DROP(mvm, "Bad REPLY_RX_MPDU_CMD size\n");
@@ -338,6 +339,11 @@ void iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct napi_struct *napi,
!(rx_pkt_status & RX_MPDU_RES_STATUS_OVERRUN_OK)) {
IWL_DEBUG_RX(mvm, "Bad CRC or FIFO: 0x%08X.\n", rx_pkt_status);
rx_status->flag |= RX_FLAG_FAILED_FCS_CRC;
+ if (!(rx_pkt_status & RX_MPDU_RES_STATUS_CRC_OK))
+ mvm->ethtool_stats.rx_crc_err++;
+ else
+ mvm->ethtool_stats.rx_fifo_underrun++;
+ bad_pkt = true;
}
/* This will be used in several places later */
@@ -406,6 +412,7 @@ void iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct napi_struct *napi,
&crypt_len)) {
IWL_DEBUG_DROP(mvm, "Bad decryption results 0x%08x\n",
rx_pkt_status);
+ mvm->ethtool_stats.rx_failed_decrypt++;
kfree_skb(skb);
rcu_read_unlock();
return;
@@ -495,6 +502,9 @@ void iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct napi_struct *napi,
rx_status->bw = RATE_INFO_BW_160;
break;
}
+ mvm->ethtool_stats.rx_bw[(rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK_V1)
+ >> RATE_MCS_CHAN_WIDTH_POS]++;
+
if (!(rate_n_flags & RATE_MCS_CCK_MSK_V1) &&
rate_n_flags & RATE_MCS_SGI_MSK_V1)
rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI;
@@ -508,6 +518,9 @@ void iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct napi_struct *napi,
rx_status->encoding = RX_ENC_HT;
rx_status->rate_idx = rate_n_flags & RATE_HT_MCS_INDEX_MSK_V1;
rx_status->enc_flags |= stbc << RX_ENC_FLAG_STBC_SHIFT;
+ mvm->ethtool_stats.rx_mode[2]++;
+ mvm->ethtool_stats.rx_nss[(rx_status->rate_idx / 8)]++;
+ mvm->ethtool_stats.rx_mcs[rx_status->rate_idx % 8]++;
} else if (rate_n_flags & RATE_MCS_VHT_MSK_V1) {
u8 stbc = (rate_n_flags & RATE_MCS_STBC_MSK) >>
RATE_MCS_STBC_POS;
@@ -518,6 +531,9 @@ void iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct napi_struct *napi,
rx_status->enc_flags |= stbc << RX_ENC_FLAG_STBC_SHIFT;
if (rate_n_flags & RATE_MCS_BF_MSK)
rx_status->enc_flags |= RX_ENC_FLAG_BF;
+ mvm->ethtool_stats.rx_mode[3]++;
+ mvm->ethtool_stats.rx_nss[rx_status->nss - 1]++;
+ mvm->ethtool_stats.rx_mcs[rx_status->rate_idx]++;
} else {
int rate = iwl_mvm_legacy_rate_to_mac80211_idx(rate_n_flags,
rx_status->band);
@@ -529,12 +545,20 @@ void iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct napi_struct *napi,
return;
}
rx_status->rate_idx = rate;
+ if (rate_n_flags & RATE_MCS_CCK_MSK_V1)
+ mvm->ethtool_stats.rx_mode[0]++;
+ else
+ mvm->ethtool_stats.rx_mode[1]++;
}
#ifdef CONFIG_IWLWIFI_DEBUGFS
iwl_mvm_update_frame_stats(mvm, rate_n_flags,
rx_status->flag & RX_FLAG_AMPDU_DETAILS);
#endif
+ if (!bad_pkt) {
+ mvm->ethtool_stats.rx_pkts++;
+ mvm->ethtool_stats.rx_bytes_nic += len;
+ }
if (unlikely((ieee80211_is_beacon(hdr->frame_control) ||
ieee80211_is_probe_resp(hdr->frame_control)) &&
@@ -135,8 +135,10 @@ static int iwl_mvm_create_skb(struct iwl_mvm *mvm, struct sk_buff *skb,
*/
hdrlen += crypt_len;
- if (unlikely(headlen < hdrlen))
+ if (unlikely(headlen < hdrlen)) {
+ mvm->ethtool_stats.rx_bad_header_len++;
return -EINVAL;
+ }
/* Since data doesn't move data while putting data on skb and that is
* the only way we use, data + len is the next place that hdr would be put
@@ -1916,6 +1918,8 @@ static void iwl_mvm_rx_eht(struct iwl_mvm *mvm, struct sk_buff *skb,
/* specific handling for 320MHz */
bw = FIELD_GET(RATE_MCS_CHAN_WIDTH_MSK, rate_n_flags);
+ mvm->ethtool_stats.rx_bw[bw]++;
+ mvm->ethtool_stats.rx_he_type[he_type >> RATE_MCS_HE_TYPE_POS]++;
if (bw == RATE_MCS_CHAN_WIDTH_320_VAL)
bw += FIELD_GET(IWL_RX_PHY_DATA0_EHT_BW320_SLOT,
le32_to_cpu(phy_data->d0));
@@ -2089,7 +2093,11 @@ static void iwl_mvm_rx_he(struct iwl_mvm *mvm, struct sk_buff *skb,
rate_n_flags & RATE_MCS_HE_106T_MSK) {
rx_status->bw = RATE_INFO_BW_HE_RU;
rx_status->he_ru = NL80211_RATE_INFO_HE_RU_ALLOC_106;
+ mvm->ethtool_stats.rx_bw_he_ru++;
+ } else {
+ mvm->ethtool_stats.rx_bw[rx_status->bw]++;
}
+ mvm->ethtool_stats.rx_he_type[he_type >> RATE_MCS_HE_TYPE_POS]++;
/* actually data is filled in mac80211 */
if (he_type == RATE_MCS_HE_TYPE_SU ||
@@ -2305,6 +2313,7 @@ static void iwl_mvm_rx_fill_status(struct iwl_mvm *mvm,
switch (format) {
case RATE_MCS_VHT_MSK:
rx_status->encoding = RX_ENC_VHT;
+ mvm->ethtool_stats.rx_bw[rx_status->bw]++;
break;
case RATE_MCS_HE_MSK:
rx_status->encoding = RX_ENC_HE;
@@ -2322,6 +2331,7 @@ static void iwl_mvm_rx_fill_status(struct iwl_mvm *mvm,
rx_status->rate_idx = RATE_HT_MCS_INDEX(rate_n_flags);
rx_status->nss = rx_status->rate_idx / 8 + 1;
rx_status->enc_flags |= stbc << RX_ENC_FLAG_STBC_SHIFT;
+ mvm->ethtool_stats.rx_bw[rx_status->bw]++;
break;
case RATE_MCS_VHT_MSK:
case RATE_MCS_HE_MSK:
@@ -2345,9 +2355,17 @@ static void iwl_mvm_rx_fill_status(struct iwl_mvm *mvm,
}
rx_status->nss = 1;
+ mvm->ethtool_stats.rx_bw[0]++;
break;
}
}
+
+ mvm->ethtool_stats.rx_mode[format >> RATE_MCS_MOD_TYPE_POS]++;
+ mvm->ethtool_stats.rx_nss[rx_status->nss - 1]++;
+ if (format == RATE_MCS_HT_MSK)
+ mvm->ethtool_stats.rx_mcs[rx_status->rate_idx % 8]++;
+ else
+ mvm->ethtool_stats.rx_mcs[rx_status->rate_idx]++;
}
/* On FPGA, AP sends beacons/probe resp on all channels causing the station
@@ -2378,6 +2396,7 @@ void iwl_mvm_rx_mpdu_mq(struct iwl_mvm *mvm, struct napi_struct *napi,
size_t desc_size;
struct iwl_mvm_rx_phy_data phy_data = {};
u32 format;
+ bool bad_pkt = false;
if (unlikely(test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)))
return;
@@ -2469,6 +2488,11 @@ void iwl_mvm_rx_mpdu_mq(struct iwl_mvm *mvm, struct napi_struct *napi,
IWL_DEBUG_RX(mvm, "Bad CRC or FIFO: 0x%08X.\n",
le32_to_cpu(desc->status));
rx_status->flag |= RX_FLAG_FAILED_FCS_CRC;
+ if (!(desc->status & cpu_to_le32(IWL_RX_MPDU_STATUS_CRC_OK)))
+ mvm->ethtool_stats.rx_crc_err++;
+ else
+ mvm->ethtool_stats.rx_fifo_underrun++;
+ bad_pkt = true;
}
/* set the preamble flag if appropriate */
@@ -2543,6 +2567,7 @@ void iwl_mvm_rx_mpdu_mq(struct iwl_mvm *mvm, struct napi_struct *napi,
le32_to_cpu(pkt->len_n_flags), queue,
&crypt_len)) {
kfree_skb(skb);
+ mvm->ethtool_stats.rx_failed_decrypt++;
goto out;
}
@@ -2623,6 +2648,7 @@ void iwl_mvm_rx_mpdu_mq(struct iwl_mvm *mvm, struct napi_struct *napi,
if (iwl_mvm_is_dup(sta, queue, rx_status, hdr, desc)) {
kfree_skb(skb);
+ mvm->ethtool_stats.rx_dup++;
goto out;
}
@@ -2671,6 +2697,15 @@ void iwl_mvm_rx_mpdu_mq(struct iwl_mvm *mvm, struct napi_struct *napi,
goto out;
}
+ if (!bad_pkt) {
+ mvm->ethtool_stats.rx_pkts++;
+ mvm->ethtool_stats.rx_bytes_nic += len;
+ }
+
+ /* NOTE: These methods below must (and will) consume the skb if the 'else'
+ * clause of the if statement will happen. So should not leak mem
+ * even though it looks problematic at first glance.
+ */
if (!iwl_mvm_reorder(mvm, napi, queue, sta, skb, desc) &&
(likely(!iwl_mvm_time_sync_frame(mvm, skb, hdr->addr2))) &&
iwl_mvm_is_valid_packet_channel(rx_status, skb)
@@ -1490,7 +1490,8 @@ static int iwl_mvm_get_hwrate_chan_width(u32 chan_width)
}
}
-void iwl_mvm_hwrate_to_tx_rate(u32 rate_n_flags,
+void iwl_mvm_hwrate_to_tx_rate(struct iwl_mvm *mvm,
+ u32 rate_n_flags,
enum nl80211_band band,
struct ieee80211_tx_rate *r)
{
@@ -1498,27 +1499,55 @@ void iwl_mvm_hwrate_to_tx_rate(u32 rate_n_flags,
u32 rate = format == RATE_MCS_HT_MSK ?
RATE_HT_MCS_INDEX(rate_n_flags) :
rate_n_flags & RATE_MCS_CODE_MSK;
+ int bwi = (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK) >> RATE_MCS_CHAN_WIDTH_POS;
r->flags |=
iwl_mvm_get_hwrate_chan_width(rate_n_flags &
RATE_MCS_CHAN_WIDTH_MSK);
+ mvm->ethtool_stats.tx_bw[bwi]++;
+ if (rate_n_flags & RATE_MCS_HE_106T_MSK)
+ mvm->ethtool_stats.tx_bw_106_tone++;
+
if (rate_n_flags & RATE_MCS_SGI_MSK)
r->flags |= IEEE80211_TX_RC_SHORT_GI;
if (format == RATE_MCS_HT_MSK) {
r->flags |= IEEE80211_TX_RC_MCS;
r->idx = rate;
+ mvm->ethtool_stats.tx_mcs[rate % 8]++; /* treat mcs like we do for VHT */
+ mvm->ethtool_stats.tx_ht++;
} else if (format == RATE_MCS_VHT_MSK) {
ieee80211_rate_set_vht(r, rate,
FIELD_GET(RATE_MCS_NSS_MSK, rate_n_flags) + 1);
r->flags |= IEEE80211_TX_RC_VHT_MCS;
+ mvm->ethtool_stats.tx_mcs[rate]++;
+ mvm->ethtool_stats.tx_vht++;
} else if (format == RATE_MCS_HE_MSK) {
/* mac80211 cannot do this without ieee80211_tx_status_ext()
- * but it only matters for radiotap */
+ * but it only matters for radiotap
+ */
+ r->idx = 0;
+ mvm->ethtool_stats.tx_mcs[rate]++;
+ mvm->ethtool_stats.tx_he++;
+ mvm->ethtool_stats.tx_he_type[(rate_n_flags >> RATE_MCS_HE_TYPE_POS) & 0x3]++;
+ } else if (format == RATE_MCS_EHT_MSK) {
+ /* mac80211 cannot do this without ieee80211_tx_status_ext()
+ * but it only matters for radiotap
+ */
r->idx = 0;
+ mvm->ethtool_stats.tx_mcs[rate]++;
+ mvm->ethtool_stats.tx_eht++;
+ mvm->ethtool_stats.tx_he_type[(rate_n_flags >> RATE_MCS_HE_TYPE_POS) & 0x3]++;
+ } else if (format == RATE_MCS_LEGACY_OFDM_MSK) {
+ r->idx = iwl_mvm_legacy_hw_idx_to_mac80211_idx(rate_n_flags,
+ band);
+ mvm->ethtool_stats.tx_mcs[rate & RATE_LEGACY_RATE_MSK]++;
+ mvm->ethtool_stats.tx_ofdm++;
} else {
r->idx = iwl_mvm_legacy_hw_idx_to_mac80211_idx(rate_n_flags,
band);
+ mvm->ethtool_stats.tx_mcs[rate & RATE_LEGACY_RATE_MSK]++;
+ mvm->ethtool_stats.tx_cck++;
}
}
@@ -1552,7 +1581,8 @@ void iwl_mvm_hwrate_to_tx_rate_v1(u32 rate_n_flags,
/*
* translate ucode response to mac80211 tx status control values
*/
-static void iwl_mvm_hwrate_to_tx_status(const struct iwl_fw *fw,
+static void iwl_mvm_hwrate_to_tx_status(struct iwl_mvm *mvm,
+ const struct iwl_fw *fw,
u32 rate_n_flags,
struct ieee80211_tx_info *info)
{
@@ -1564,7 +1594,12 @@ static void iwl_mvm_hwrate_to_tx_status(const struct iwl_fw *fw,
info->status.antenna =
((rate_n_flags & RATE_MCS_ANT_AB_MSK) >> RATE_MCS_ANT_POS);
- iwl_mvm_hwrate_to_tx_rate(rate_n_flags,
+ if (info->status.antenna == 0x3)
+ mvm->ethtool_stats.tx_nss[1]++;
+ else
+ mvm->ethtool_stats.tx_nss[0]++;
+
+ iwl_mvm_hwrate_to_tx_rate(mvm, rate_n_flags,
info->band, r);
}
@@ -1609,6 +1644,20 @@ static void iwl_mvm_tx_status_check_trigger(struct iwl_mvm *mvm,
}
}
+static void iwl_mvm_update_tx_stats(struct iwl_mvm *mvm, struct sk_buff *skb, u32 status)
+{
+ u32 idx = status & TX_STATUS_MSK;
+
+ if (idx > TX_STATUS_INTERNAL_ABORT + 1)
+ idx = TX_STATUS_INTERNAL_ABORT + 1;
+
+ mvm->ethtool_stats.tx_status_counts[idx]++;
+ if (idx == TX_STATUS_SUCCESS)
+ mvm->ethtool_stats.tx_bytes_nic += skb->len;
+ else
+ mvm->ethtool_stats.tx_mpdu_fail++;
+}
+
/*
* iwl_mvm_get_scd_ssn - returns the SSN of the SCD
* @tx_resp: the Tx response from the fw (agg or non-agg)
@@ -1629,6 +1678,41 @@ static inline u32 iwl_mvm_get_scd_ssn(struct iwl_mvm *mvm,
tx_resp->frame_count) & 0xfff;
}
+static void iwl_mvm_update_tx_ampdu_histogram(struct iwl_mvm *mvm, int freed)
+{
+ /* tx-ampdu-len histogram, buckets match what mtk7915 supports. */
+ if (freed <= 1)
+ mvm->ethtool_stats.tx_ampdu_len[0]++;
+ else if (freed <= 10)
+ mvm->ethtool_stats.tx_ampdu_len[1]++;
+ else if (freed <= 19)
+ mvm->ethtool_stats.tx_ampdu_len[2]++;
+ else if (freed <= 28)
+ mvm->ethtool_stats.tx_ampdu_len[3]++;
+ else if (freed <= 37)
+ mvm->ethtool_stats.tx_ampdu_len[4]++;
+ else if (freed <= 46)
+ mvm->ethtool_stats.tx_ampdu_len[5]++;
+ else if (freed <= 55)
+ mvm->ethtool_stats.tx_ampdu_len[6]++;
+ else if (freed <= 79)
+ mvm->ethtool_stats.tx_ampdu_len[7]++;
+ else if (freed <= 103)
+ mvm->ethtool_stats.tx_ampdu_len[8]++;
+ else if (freed <= 127)
+ mvm->ethtool_stats.tx_ampdu_len[9]++;
+ else if (freed <= 151)
+ mvm->ethtool_stats.tx_ampdu_len[10]++;
+ else if (freed <= 175)
+ mvm->ethtool_stats.tx_ampdu_len[11]++;
+ else if (freed <= 199)
+ mvm->ethtool_stats.tx_ampdu_len[12]++;
+ else if (freed <= 223)
+ mvm->ethtool_stats.tx_ampdu_len[13]++;
+ else
+ mvm->ethtool_stats.tx_ampdu_len[14]++;
+}
+
static void iwl_mvm_rx_tx_cmd_single(struct iwl_mvm *mvm,
struct iwl_rx_packet *pkt)
{
@@ -1659,6 +1743,8 @@ static void iwl_mvm_rx_tx_cmd_single(struct iwl_mvm *mvm,
/* we can free until ssn % q.n_bd not inclusive */
iwl_trans_reclaim(mvm->trans, txq_id, ssn, &skbs);
+ iwl_mvm_update_tx_ampdu_histogram(mvm, tx_resp->frame_count);
+
while (!skb_queue_empty(&skbs)) {
struct sk_buff *skb = __skb_dequeue(&skbs);
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
@@ -1710,7 +1796,10 @@ static void iwl_mvm_rx_tx_cmd_single(struct iwl_mvm *mvm,
info->status.rates[0].count = tx_resp->failure_frame + 1;
- iwl_mvm_hwrate_to_tx_status(mvm->fw,
+ mvm->ethtool_stats.tx_mpdu_attempts += info->status.rates[0].count;
+ mvm->ethtool_stats.tx_mpdu_retry += tx_resp->failure_frame;
+
+ iwl_mvm_hwrate_to_tx_status(mvm, mvm->fw,
le32_to_cpu(tx_resp->initial_rate),
info);
@@ -1757,6 +1846,8 @@ static void iwl_mvm_rx_tx_cmd_single(struct iwl_mvm *mvm,
info->status.status_driver_data[0] =
RS_DRV_DATA_PACK(lq_color, tx_resp->reduced_tpc);
+ iwl_mvm_update_tx_stats(mvm, skb, status);
+
#ifdef CONFIG_IWLMVM_TDLS_PEER_CACHE
if (info->flags & IEEE80211_TX_STAT_ACK)
iwl_mvm_tdls_peer_cache_pkt(mvm, (void *)skb->data,
@@ -1975,6 +2066,9 @@ void iwl_mvm_rx_tx_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb)
struct iwl_rx_packet *pkt = rxb_addr(rxb);
struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data;
+ /* tx-ampdu-len histogram, buckets match what mtk7915 supports. */
+ iwl_mvm_update_tx_ampdu_histogram(mvm, tx_resp->frame_count);
+
if (tx_resp->frame_count == 1)
iwl_mvm_rx_tx_cmd_single(mvm, pkt);
else
@@ -2070,12 +2164,19 @@ static void iwl_mvm_tx_reclaim(struct iwl_mvm *mvm, int sta_id, int tid,
skb_queue_walk(&reclaimed_skbs, skb) {
struct ieee80211_hdr *hdr = (void *)skb->data;
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ bool do_status = false;
if (!is_flush) {
- if (ieee80211_is_data_qos(hdr->frame_control))
+ if (ieee80211_is_data_qos(hdr->frame_control)) {
freed++;
- else
+ do_status = true;
+ } else {
WARN_ON_ONCE(tid != IWL_MAX_TID_COUNT);
+ }
+
+ mvm->ethtool_stats.tx_status_counts[TX_STATUS_SUCCESS]++;
+ mvm->ethtool_stats.tx_mpdu_attempts++;
+ mvm->ethtool_stats.tx_bytes_nic += skb->len;
}
#ifdef CONFIG_IWLMVM_TDLS_PEER_CACHE
@@ -2088,10 +2189,17 @@ static void iwl_mvm_tx_reclaim(struct iwl_mvm *mvm, int sta_id, int tid,
info->flags |= IEEE80211_TX_STAT_AMPDU;
memcpy(&info->status, &tx_info->status,
sizeof(tx_info->status));
- iwl_mvm_hwrate_to_tx_status(mvm->fw, rate, info);
}
+
+ /* Call this for all non-flushed data frames, not just the
+ * first freed one, in order to get proper tx ethtool stats.
+ */
+ if (do_status)
+ iwl_mvm_hwrate_to_tx_status(mvm, mvm->fw, rate, info);
}
+ iwl_mvm_update_tx_ampdu_histogram(mvm, freed);
+
spin_unlock_bh(&mvmsta->lock);
/* We got a BA notif with 0 acked or scd_ssn didn't progress which is
@@ -2111,7 +2219,7 @@ static void iwl_mvm_tx_reclaim(struct iwl_mvm *mvm, int sta_id, int tid,
goto out;
tx_info->band = chanctx_conf->def.chan->band;
- iwl_mvm_hwrate_to_tx_status(mvm->fw, rate, tx_info);
+ iwl_mvm_hwrate_to_tx_status(mvm, mvm->fw, rate, tx_info);
IWL_DEBUG_TX_REPLY(mvm, "No reclaim. Update rs directly\n");
iwl_mvm_rs_tx_status(mvm, sta, tid, tx_info, false);