From: Johannes Berg <johannes.b...@intel.com>

Try to detect that the AP is not using aggregation even when there's
enough traffic to make it worthwhile; if this is the case and U-APSD
is enabled then assume the AP is broken (like so many) and doesn't
enable aggregation when U-APSD is used. In this case, disconnect from
the AP and blacklist U-APSD for a potential new connection to it.

Signed-off-by: Johannes Berg <johannes.b...@intel.com>
Signed-off-by: Luca Coelho <luciano.coe...@intel.com>
---
 drivers/net/wireless/intel/iwlwifi/mvm/constants.h |   4 +
 drivers/net/wireless/intel/iwlwifi/mvm/debugfs.c   |  25 ++++
 drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c  |  33 ++++++
 drivers/net/wireless/intel/iwlwifi/mvm/mvm.h       |   9 ++
 drivers/net/wireless/intel/iwlwifi/mvm/rx.c        |  47 ++++++++
 drivers/net/wireless/intel/iwlwifi/mvm/utils.c     | 127 ++++++++++++++++++++-
 6 files changed, 244 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/constants.h 
b/drivers/net/wireless/intel/iwlwifi/mvm/constants.h
index 2763fc69f04b..d61ff66ce07b 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/constants.h
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/constants.h
@@ -69,6 +69,8 @@
 
 #include <linux/ieee80211.h>
 
+#define IWL_MVM_UAPSD_NOAGG_BSSIDS_NUM         20
+
 #define IWL_MVM_DEFAULT_PS_TX_DATA_TIMEOUT     (100 * USEC_PER_MSEC)
 #define IWL_MVM_DEFAULT_PS_RX_DATA_TIMEOUT     (100 * USEC_PER_MSEC)
 #define IWL_MVM_WOWLAN_PS_TX_DATA_TIMEOUT      (10 * USEC_PER_MSEC)
@@ -115,6 +117,8 @@
 #define IWL_MVM_TCM_LOAD_MEDIUM_THRESH         10 /* percentage */
 #define IWL_MVM_TCM_LOAD_HIGH_THRESH           50 /* percentage */
 #define IWL_MVM_TCM_LOWLAT_ENABLE_THRESH       100 /* packets/10 seconds */
+#define IWL_MVM_UAPSD_NONAGG_PERIOD            5000 /* msecs */
+#define IWL_MVM_UAPSD_NOAGG_LIST_LEN           IWL_MVM_UAPSD_NOAGG_BSSIDS_NUM
 #define IWL_MVM_RS_NUM_TRY_BEFORE_ANT_TOGGLE    1
 #define IWL_MVM_RS_HT_VHT_RETRIES_PER_RATE      2
 #define IWL_MVM_RS_HT_VHT_RETRIES_PER_RATE_TW   1
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/debugfs.c 
b/drivers/net/wireless/intel/iwlwifi/mvm/debugfs.c
index 0e6401cd7ccc..1c4178f20441 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/debugfs.c
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/debugfs.c
@@ -1728,6 +1728,27 @@ iwl_dbgfs_send_echo_cmd_write(struct iwl_mvm *mvm, char 
*buf,
        return ret ?: count;
 }
 
+static ssize_t
+iwl_dbgfs_uapsd_noagg_bssids_read(struct file *file, char __user *user_buf,
+                                 size_t count, loff_t *ppos)
+{
+       struct iwl_mvm *mvm = file->private_data;
+       u8 buf[IWL_MVM_UAPSD_NOAGG_BSSIDS_NUM * ETH_ALEN * 3 + 1];
+       unsigned int pos = 0;
+       size_t bufsz = sizeof(buf);
+       int i;
+
+       mutex_lock(&mvm->mutex);
+
+       for (i = 0; i < IWL_MVM_UAPSD_NOAGG_LIST_LEN; i++)
+               pos += scnprintf(buf + pos, bufsz - pos, "%pM\n",
+                                mvm->uapsd_noagg_bssids[i].addr);
+
+       mutex_unlock(&mvm->mutex);
+
+       return simple_read_from_buffer(user_buf, count, ppos, buf, pos);
+}
+
 MVM_DEBUGFS_READ_WRITE_FILE_OPS(prph_reg, 64);
 
 /* Device wide debugfs entries */
@@ -1762,6 +1783,8 @@ MVM_DEBUGFS_WRITE_FILE_OPS(indirection_tbl,
                           (IWL_RSS_INDIRECTION_TABLE_SIZE * 2));
 MVM_DEBUGFS_WRITE_FILE_OPS(inject_packet, 512);
 
+MVM_DEBUGFS_READ_FILE_OPS(uapsd_noagg_bssids);
+
 #ifdef CONFIG_IWLWIFI_BCAST_FILTERING
 MVM_DEBUGFS_READ_WRITE_FILE_OPS(bcast_filters, 256);
 MVM_DEBUGFS_READ_WRITE_FILE_OPS(bcast_filters_macs, 256);
@@ -1972,6 +1995,8 @@ int iwl_mvm_dbgfs_register(struct iwl_mvm *mvm, struct 
dentry *dbgfs_dir)
                                 mvm->debugfs_dir, &mvm->drop_bcn_ap_mode))
                goto err;
 
+       MVM_DEBUGFS_ADD_FILE(uapsd_noagg_bssids, mvm->debugfs_dir, S_IRUSR);
+
 #ifdef CONFIG_IWLWIFI_BCAST_FILTERING
        if (mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_BCAST_FILTERING) {
                bcast_dir = debugfs_create_dir("bcast_filtering",
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c 
b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c
index 51b30424575b..08318bdaaf2e 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c
@@ -952,6 +952,16 @@ static int iwl_mvm_mac_ampdu_action(struct ieee80211_hw 
*hw,
 
        switch (action) {
        case IEEE80211_AMPDU_RX_START:
+               if (iwl_mvm_vif_from_mac80211(vif)->ap_sta_id ==
+                               iwl_mvm_sta_from_mac80211(sta)->sta_id) {
+                       struct iwl_mvm_vif *mvmvif;
+                       u16 macid = iwl_mvm_vif_from_mac80211(vif)->id;
+                       struct iwl_mvm_tcm_mac *mdata = &mvm->tcm.data[macid];
+
+                       mdata->opened_rx_ba_sessions = true;
+                       mvmvif = iwl_mvm_vif_from_mac80211(vif);
+                       cancel_delayed_work(&mvmvif->uapsd_nonagg_detected_wk);
+               }
                if (!iwl_enable_rx_ampdu(mvm->cfg)) {
                        ret = -EINVAL;
                        break;
@@ -1435,6 +1445,8 @@ static int iwl_mvm_mac_add_interface(struct ieee80211_hw 
*hw,
                mvm->p2p_device_vif = vif;
        }
 
+       iwl_mvm_tcm_add_vif(mvm, vif);
+
        if (vif->type == NL80211_IFTYPE_MONITOR)
                mvm->monitor_on = true;
 
@@ -1486,6 +1498,10 @@ static void iwl_mvm_mac_remove_interface(struct 
ieee80211_hw *hw,
 
        iwl_mvm_prepare_mac_removal(mvm, vif);
 
+       if (!(vif->type == NL80211_IFTYPE_AP ||
+             vif->type == NL80211_IFTYPE_ADHOC))
+               iwl_mvm_tcm_rm_vif(mvm, vif);
+
        mutex_lock(&mvm->mutex);
 
        if (mvm->bf_allowed_vif == mvmvif) {
@@ -2535,6 +2551,16 @@ static void iwl_mvm_sta_pre_rcu_remove(struct 
ieee80211_hw *hw,
 static void iwl_mvm_check_uapsd(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
                                const u8 *bssid)
 {
+       int i;
+
+       if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) {
+               struct iwl_mvm_tcm_mac *mdata;
+
+               mdata = &mvm->tcm.data[iwl_mvm_vif_from_mac80211(vif)->id];
+               ewma_rate_init(&mdata->uapsd_nonagg_detect.rate);
+               mdata->opened_rx_ba_sessions = false;
+       }
+
        if (!(mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_UAPSD_SUPPORT))
                return;
 
@@ -2549,6 +2575,13 @@ static void iwl_mvm_check_uapsd(struct iwl_mvm *mvm, 
struct ieee80211_vif *vif,
                return;
        }
 
+       for (i = 0; i < IWL_MVM_UAPSD_NOAGG_LIST_LEN; i++) {
+               if (ether_addr_equal(mvm->uapsd_noagg_bssids[i].addr, bssid)) {
+                       vif->driver_flags &= ~IEEE80211_VIF_SUPPORTS_UAPSD;
+                       return;
+               }
+       }
+
        vif->driver_flags |= IEEE80211_VIF_SUPPORTS_UAPSD;
 }
 
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h 
b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h
index 8d1a8c6c8207..7fa68f4f2f6b 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/mvm.h
@@ -446,6 +446,8 @@ struct iwl_mvm_vif {
        /* FW identified misbehaving AP */
        u8 uapsd_misbehaving_bssid[ETH_ALEN];
 
+       struct delayed_work uapsd_nonagg_detected_wk;
+
        /* Indicates that CSA countdown may be started */
        bool csa_countdown;
        bool csa_failed;
@@ -621,6 +623,7 @@ struct iwl_mvm_tcm_mac {
                struct ewma_rate rate;
                bool detected;
        } uapsd_nonagg_detect;
+       bool opened_rx_ba_sessions;
 };
 
 struct iwl_mvm_tcm {
@@ -1028,6 +1031,10 @@ struct iwl_mvm {
        unsigned long bt_coex_last_tcm_ts;
        struct iwl_mvm_tcm tcm;
 
+       u8 uapsd_noagg_bssid_write_idx;
+       struct mac_address uapsd_noagg_bssids[IWL_MVM_UAPSD_NOAGG_BSSIDS_NUM]
+               __aligned(2);
+
        struct iwl_time_quota_cmd last_quota_cmd;
 
 #ifdef CONFIG_NL80211_TESTMODE
@@ -1964,6 +1971,8 @@ void iwl_mvm_tcm_work(struct work_struct *work);
 void iwl_mvm_recalc_tcm(struct iwl_mvm *mvm);
 void iwl_mvm_pause_tcm(struct iwl_mvm *mvm, bool with_cancel);
 void iwl_mvm_resume_tcm(struct iwl_mvm *mvm);
+void iwl_mvm_tcm_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif);
+void iwl_mvm_tcm_rm_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif);
 u8 iwl_mvm_tcm_load_percentage(u32 airtime, u32 elapsed);
 
 void iwl_mvm_nic_restart(struct iwl_mvm *mvm, bool fw_error);
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c 
b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c
index be80294349c3..bfb163419c67 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c
@@ -264,6 +264,12 @@ static void iwl_mvm_rx_handle_tcm(struct iwl_mvm *mvm,
        struct iwl_mvm_tcm_mac *mdata;
        int mac;
        int ac = IEEE80211_AC_BE; /* treat non-QoS as BE */
+       struct iwl_mvm_vif *mvmvif;
+       /* expected throughput in 100Kbps, single stream, 20 MHz */
+       static const u8 thresh_tpt[] = {
+               9, 18, 30, 42, 60, 78, 90, 96, 120, 135,
+       };
+       u16 thr;
 
        if (ieee80211_is_data_qos(hdr->frame_control))
                ac = tid_to_mac80211_ac[ieee80211_get_tid(hdr)];
@@ -285,6 +291,35 @@ static void iwl_mvm_rx_handle_tcm(struct iwl_mvm *mvm,
        if (!(rate_n_flags & (RATE_MCS_HT_MSK | RATE_MCS_VHT_MSK)))
                return;
 
+       mvmvif = iwl_mvm_vif_from_mac80211(mvmsta->vif);
+
+       if (mdata->opened_rx_ba_sessions ||
+           mdata->uapsd_nonagg_detect.detected ||
+           (!mvmvif->queue_params[IEEE80211_AC_VO].uapsd &&
+            !mvmvif->queue_params[IEEE80211_AC_VI].uapsd &&
+            !mvmvif->queue_params[IEEE80211_AC_BE].uapsd &&
+            !mvmvif->queue_params[IEEE80211_AC_BK].uapsd) ||
+           mvmsta->sta_id != mvmvif->ap_sta_id)
+               return;
+
+       if (rate_n_flags & RATE_MCS_HT_MSK) {
+               thr = thresh_tpt[rate_n_flags & RATE_HT_MCS_RATE_CODE_MSK];
+               thr *= 1 + ((rate_n_flags & RATE_HT_MCS_NSS_MSK) >>
+                                       RATE_HT_MCS_NSS_POS);
+       } else {
+               if (WARN_ON((rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK) >=
+                               ARRAY_SIZE(thresh_tpt)))
+                       return;
+               thr = thresh_tpt[rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK];
+               thr *= 1 + ((rate_n_flags & RATE_VHT_MCS_NSS_MSK) >>
+                                       RATE_VHT_MCS_NSS_POS);
+       }
+
+       thr <<= ((rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK) >>
+                               RATE_MCS_CHAN_WIDTH_POS);
+
+       mdata->uapsd_nonagg_detect.rx_bytes += len;
+       ewma_rate_add(&mdata->uapsd_nonagg_detect.rate, thr);
 }
 
 static void iwl_mvm_rx_csum(struct ieee80211_sta *sta,
@@ -693,6 +728,7 @@ void iwl_mvm_handle_rx_statistics(struct iwl_mvm *mvm,
        int expected_size;
        int i;
        u8 *energy;
+       __le32 *bytes;
        __le32 *air_time;
        __le32 flags;
 
@@ -768,11 +804,13 @@ void iwl_mvm_handle_rx_statistics(struct iwl_mvm *mvm,
                struct iwl_notif_statistics_v11 *v11 = (void *)&pkt->data;
 
                energy = (void *)&v11->load_stats.avg_energy;
+               bytes = (void *)&v11->load_stats.byte_count;
                air_time = (void *)&v11->load_stats.air_time;
        } else {
                struct iwl_notif_statistics_cdb *stats = (void *)&pkt->data;
 
                energy = (void *)&stats->load_stats.avg_energy;
+               bytes = (void *)&stats->load_stats.byte_count;
                air_time = (void *)&stats->load_stats.air_time;
        }
 
@@ -802,6 +840,15 @@ void iwl_mvm_handle_rx_statistics(struct iwl_mvm *mvm,
        for (i = 0; i < NUM_MAC_INDEX_DRIVER; i++) {
                struct iwl_mvm_tcm_mac *mdata = &mvm->tcm.data[i];
                u32 airtime = le32_to_cpu(air_time[i]);
+               u32 rx_bytes = le32_to_cpu(bytes[i]);
+
+               mdata->uapsd_nonagg_detect.rx_bytes += rx_bytes;
+               if (airtime) {
+                       /* re-init every time to store rate from FW */
+                       ewma_rate_init(&mdata->uapsd_nonagg_detect.rate);
+                       ewma_rate_add(&mdata->uapsd_nonagg_detect.rate,
+                                     rx_bytes * 8 / airtime);
+               }
 
                mdata->rx.airtime += airtime;
        }
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c 
b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c
index f61df5b5072f..525a4b06ea15 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c
@@ -1510,12 +1510,109 @@ static void iwl_mvm_tcm_results(struct iwl_mvm *mvm)
        mutex_unlock(&mvm->mutex);
 }
 
+static void iwl_mvm_tcm_uapsd_nonagg_detected_wk(struct work_struct *wk)
+{
+       struct iwl_mvm *mvm;
+       struct iwl_mvm_vif *mvmvif;
+       struct ieee80211_vif *vif;
+
+       mvmvif = container_of(wk, struct iwl_mvm_vif,
+                             uapsd_nonagg_detected_wk.work);
+       vif = container_of((void *)mvmvif, struct ieee80211_vif, drv_priv);
+       mvm = mvmvif->mvm;
+
+       if (mvm->tcm.data[mvmvif->id].opened_rx_ba_sessions)
+               return;
+
+       /* remember that this AP is broken */
+       memcpy(mvm->uapsd_noagg_bssids[mvm->uapsd_noagg_bssid_write_idx].addr,
+              vif->bss_conf.bssid, ETH_ALEN);
+       mvm->uapsd_noagg_bssid_write_idx++;
+       if (mvm->uapsd_noagg_bssid_write_idx >= IWL_MVM_UAPSD_NOAGG_LIST_LEN)
+               mvm->uapsd_noagg_bssid_write_idx = 0;
+
+       iwl_mvm_connection_loss(mvm, vif,
+                               "AP isn't using AMPDU with uAPSD enabled");
+}
+
+static void iwl_mvm_uapsd_agg_disconnect_iter(void *data, u8 *mac,
+                                             struct ieee80211_vif *vif)
+{
+       struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
+       struct iwl_mvm *mvm = mvmvif->mvm;
+       int *mac_id = data;
+
+       if (vif->type != NL80211_IFTYPE_STATION)
+               return;
+
+       if (mvmvif->id != *mac_id)
+               return;
+
+       if (!vif->bss_conf.assoc)
+               return;
+
+       if (!mvmvif->queue_params[IEEE80211_AC_VO].uapsd &&
+           !mvmvif->queue_params[IEEE80211_AC_VI].uapsd &&
+           !mvmvif->queue_params[IEEE80211_AC_BE].uapsd &&
+           !mvmvif->queue_params[IEEE80211_AC_BK].uapsd)
+               return;
+
+       if (mvm->tcm.data[*mac_id].uapsd_nonagg_detect.detected)
+               return;
+
+       mvm->tcm.data[*mac_id].uapsd_nonagg_detect.detected = true;
+       IWL_INFO(mvm,
+                "detected AP should do aggregation but isn't, likely due to 
U-APSD\n");
+       schedule_delayed_work(&mvmvif->uapsd_nonagg_detected_wk, 15 * HZ);
+}
+
+static void iwl_mvm_check_uapsd_agg_expected_tpt(struct iwl_mvm *mvm,
+                                                unsigned int elapsed,
+                                                int mac)
+{
+       u64 bytes = mvm->tcm.data[mac].uapsd_nonagg_detect.rx_bytes;
+       u64 tpt;
+       unsigned long rate;
+
+       rate = ewma_rate_read(&mvm->tcm.data[mac].uapsd_nonagg_detect.rate);
+
+       if (!rate || mvm->tcm.data[mac].opened_rx_ba_sessions ||
+           mvm->tcm.data[mac].uapsd_nonagg_detect.detected)
+               return;
+
+       if (iwl_mvm_has_new_rx_api(mvm)) {
+               tpt = 8 * bytes; /* kbps */
+               do_div(tpt, elapsed);
+               rate *= 1000; /* kbps */
+               if (tpt < 22 * rate / 100)
+                       return;
+       } else {
+               /*
+                * the rate here is actually the threshold, in 100Kbps units,
+                * so do the needed conversion from bytes to 100Kbps:
+                * 100kb = bits / (100 * 1000),
+                * 100kbps = 100kb / (msecs / 1000) ==
+                *           (bits / (100 * 1000)) / (msecs / 1000) ==
+                *           bits / (100 * msecs)
+                */
+               tpt = (8 * bytes);
+               do_div(tpt, elapsed * 100);
+               if (tpt < rate)
+                       return;
+       }
+
+       ieee80211_iterate_active_interfaces_atomic(
+               mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
+               iwl_mvm_uapsd_agg_disconnect_iter, &mac);
+}
 
 static unsigned long iwl_mvm_calc_tcm_stats(struct iwl_mvm *mvm,
                                            unsigned long ts,
                                            bool handle_uapsd)
 {
        unsigned int elapsed = jiffies_to_msecs(ts - mvm->tcm.ts);
+       unsigned int uapsd_elapsed =
+               jiffies_to_msecs(ts - mvm->tcm.uapsd_nonagg_ts);
        u32 total_airtime = 0;
        int ac, mac;
        bool low_latency = false;
@@ -1558,6 +1655,12 @@ static unsigned long iwl_mvm_calc_tcm_stats(struct 
iwl_mvm *mvm,
                }
                low_latency |= mvm->tcm.result.low_latency[mac];
 
+               if (!mvm->tcm.result.low_latency[mac] && handle_uapsd)
+                       iwl_mvm_check_uapsd_agg_expected_tpt(mvm, uapsd_elapsed,
+                                                            mac);
+               /* clear old data */
+               if (handle_uapsd)
+                       mdata->uapsd_nonagg_detect.rx_bytes = 0;
                memset(&mdata->rx.airtime, 0, sizeof(mdata->rx.airtime));
                memset(&mdata->tx.airtime, 0, sizeof(mdata->tx.airtime));
        }
@@ -1599,7 +1702,8 @@ void iwl_mvm_recalc_tcm(struct iwl_mvm *mvm)
 {
        unsigned long ts = jiffies;
        bool handle_uapsd =
-               false;
+               time_after(ts, mvm->tcm.uapsd_nonagg_ts +
+                              msecs_to_jiffies(IWL_MVM_UAPSD_NONAGG_PERIOD));
 
        spin_lock(&mvm->tcm.lock);
        if (mvm->tcm.paused || !time_after(ts, mvm->tcm.ts + MVM_TCM_PERIOD)) {
@@ -1608,6 +1712,12 @@ void iwl_mvm_recalc_tcm(struct iwl_mvm *mvm)
        }
        spin_unlock(&mvm->tcm.lock);
 
+       if (handle_uapsd && iwl_mvm_has_new_rx_api(mvm)) {
+               mutex_lock(&mvm->mutex);
+               if (iwl_mvm_request_statistics(mvm, true))
+                       handle_uapsd = false;
+               mutex_unlock(&mvm->mutex);
+       }
 
        spin_lock(&mvm->tcm.lock);
        /* re-check if somebody else won the recheck race */
@@ -1666,6 +1776,21 @@ void iwl_mvm_resume_tcm(struct iwl_mvm *mvm)
        spin_unlock_bh(&mvm->tcm.lock);
 }
 
+void iwl_mvm_tcm_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
+{
+       struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
+
+       INIT_DELAYED_WORK(&mvmvif->uapsd_nonagg_detected_wk,
+                         iwl_mvm_tcm_uapsd_nonagg_detected_wk);
+}
+
+void iwl_mvm_tcm_rm_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
+{
+       struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
+
+       cancel_delayed_work_sync(&mvmvif->uapsd_nonagg_detected_wk);
+}
+
 
 void iwl_mvm_get_sync_time(struct iwl_mvm *mvm, u32 *gp2, u64 *boottime)
 {
-- 
2.16.3

Reply via email to