ath11k: implement hardware data filter
authorCarl Huang <quic_cjhuang@quicinc.com>
Mon, 14 Mar 2022 05:18:16 +0000 (07:18 +0200)
committerKalle Valo <quic_kvalo@quicinc.com>
Fri, 18 Mar 2022 15:35:20 +0000 (17:35 +0200)
Host needs to set hardware data filter before entering WoW to
let firmware drop needless broadcast/mulitcast frames to avoid
frequent wakeup. Host clears hardware data filter when leaving WoW.

Tested-on: QCA6390 hw2.0 PCI WLAN.HST.1.0.1-01740-QCAHSTSWPLZ_V2_TO_X86-1

Signed-off-by: Carl Huang <quic_cjhuang@quicinc.com>
Signed-off-by: Kalle Valo <quic_kvalo@quicinc.com>
Link: https://lore.kernel.org/r/1644308006-22784-4-git-send-email-quic_cjhuang@quicinc.com
drivers/net/wireless/ath/ath11k/wmi.c
drivers/net/wireless/ath/ath11k/wmi.h
drivers/net/wireless/ath/ath11k/wow.c

index d4d34b9..62b13f5 100644 (file)
@@ -8165,6 +8165,39 @@ void ath11k_wmi_detach(struct ath11k_base *ab)
        ath11k_wmi_free_dbring_caps(ab);
 }
 
+int ath11k_wmi_hw_data_filter_cmd(struct ath11k *ar, u32 vdev_id,
+                                 u32 filter_bitmap, bool enable)
+{
+       struct wmi_hw_data_filter_cmd *cmd;
+       struct sk_buff *skb;
+       int len;
+
+       len = sizeof(*cmd);
+       skb = ath11k_wmi_alloc_skb(ar->wmi->wmi_ab, len);
+
+       if (!skb)
+               return -ENOMEM;
+
+       cmd = (struct wmi_hw_data_filter_cmd *)skb->data;
+       cmd->tlv_header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_HW_DATA_FILTER_CMD) |
+                         FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE);
+
+       cmd->vdev_id = vdev_id;
+       cmd->enable = enable;
+
+       /* Set all modes in case of disable */
+       if (cmd->enable)
+               cmd->hw_filter_bitmap = filter_bitmap;
+       else
+               cmd->hw_filter_bitmap = ((u32)~0U);
+
+       ath11k_dbg(ar->ab, ATH11K_DBG_WMI,
+                  "wmi hw data filter enable %d filter_bitmap 0x%x\n",
+                  enable, filter_bitmap);
+
+       return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_HW_DATA_FILTER_CMDID);
+}
+
 int ath11k_wmi_wow_host_wakeup_ind(struct ath11k *ar)
 {
        struct wmi_wow_host_wakeup_ind *cmd;
index 9150e05..17e5098 100644 (file)
@@ -5390,6 +5390,19 @@ struct ath11k_wmi_base {
        struct ath11k_targ_cap *targ_cap;
 };
 
+/* Definition of HW data filtering */
+enum hw_data_filter_type {
+       WMI_HW_DATA_FILTER_DROP_NON_ARP_BC = BIT(0),
+       WMI_HW_DATA_FILTER_DROP_NON_ICMPV6_MC = BIT(1),
+};
+
+struct wmi_hw_data_filter_cmd {
+       u32 tlv_header;
+       u32 vdev_id;
+       u32 enable;
+       u32 hw_filter_bitmap;
+} __packed;
+
 /* WOW structures */
 enum wmi_wow_wakeup_event {
        WOW_BMISS_EVENT = 0,
@@ -5953,4 +5966,6 @@ int ath11k_wmi_wow_add_pattern(struct ath11k *ar, u32 vdev_id, u32 pattern_id,
 int ath11k_wmi_wow_add_wakeup_event(struct ath11k *ar, u32 vdev_id,
                                    enum wmi_wow_wakeup_event event,
                                    u32 enable);
+int ath11k_wmi_hw_data_filter_cmd(struct ath11k *ar, u32 vdev_id,
+                                 u32 filter_bitmap, bool enable);
 #endif
index 5f3aa0b..50dd509 100644 (file)
@@ -514,6 +514,50 @@ static int ath11k_wow_nlo_cleanup(struct ath11k *ar)
        return 0;
 }
 
+static int ath11k_wow_set_hw_filter(struct ath11k *ar)
+{
+       struct ath11k_vif *arvif;
+       u32 bitmap;
+       int ret;
+
+       lockdep_assert_held(&ar->conf_mutex);
+
+       list_for_each_entry(arvif, &ar->arvifs, list) {
+               bitmap = WMI_HW_DATA_FILTER_DROP_NON_ICMPV6_MC |
+                       WMI_HW_DATA_FILTER_DROP_NON_ARP_BC;
+               ret = ath11k_wmi_hw_data_filter_cmd(ar, arvif->vdev_id,
+                                                   bitmap,
+                                                   true);
+               if (ret) {
+                       ath11k_warn(ar->ab, "failed to set hw data filter on vdev %i: %d\n",
+                                   arvif->vdev_id, ret);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static int ath11k_wow_clear_hw_filter(struct ath11k *ar)
+{
+       struct ath11k_vif *arvif;
+       int ret;
+
+       lockdep_assert_held(&ar->conf_mutex);
+
+       list_for_each_entry(arvif, &ar->arvifs, list) {
+               ret = ath11k_wmi_hw_data_filter_cmd(ar, arvif->vdev_id, 0, false);
+
+               if (ret) {
+                       ath11k_warn(ar->ab, "failed to clear hw data filter on vdev %i: %d\n",
+                                   arvif->vdev_id, ret);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
 int ath11k_wow_op_suspend(struct ieee80211_hw *hw,
                          struct cfg80211_wowlan *wowlan)
 {
@@ -542,6 +586,13 @@ int ath11k_wow_op_suspend(struct ieee80211_hw *hw,
                goto cleanup;
        }
 
+       ret = ath11k_wow_set_hw_filter(ar);
+       if (ret) {
+               ath11k_warn(ar->ab, "failed to set hw filter: %d\n",
+                           ret);
+               goto cleanup;
+       }
+
        ret = ath11k_wow_enable(ar->ab);
        if (ret) {
                ath11k_warn(ar->ab, "failed to start wow: %d\n", ret);
@@ -610,6 +661,12 @@ int ath11k_wow_op_resume(struct ieee80211_hw *hw)
                goto exit;
        }
 
+       ret = ath11k_wow_clear_hw_filter(ar);
+       if (ret) {
+               ath11k_warn(ar->ab, "failed to clear hw filter: %d\n", ret);
+               goto exit;
+       }
+
 exit:
        if (ret) {
                switch (ar->state) {