]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: ath12k: support ARP and NS offload
authorBaochen Qiang <quic_bqiang@quicinc.com>
Wed, 19 Jun 2024 14:21:12 +0000 (17:21 +0300)
committerKalle Valo <quic_kvalo@quicinc.com>
Mon, 24 Jun 2024 16:31:47 +0000 (19:31 +0300)
Support ARP and NS offload in WoW state.

Tested this way: put machine A with QCA6390 to WoW state,
ping/ping6 machine A from another machine B, check sniffer to see
any ARP response and Neighbor Advertisement from machine A.

Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0-03427-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.15378.4

Signed-off-by: Baochen Qiang <quic_bqiang@quicinc.com>
Acked-by: Jeff Johnson <quic_jjohnson@quicinc.com>
Signed-off-by: Kalle Valo <quic_kvalo@quicinc.com>
Link: https://patch.msgid.link/20240604055407.12506-7-quic_bqiang@quicinc.com
drivers/net/wireless/ath/ath12k/mac.c
drivers/net/wireless/ath/ath12k/wmi.c
drivers/net/wireless/ath/ath12k/wmi.h
drivers/net/wireless/ath/ath12k/wow.c

index cd8fb3797eea0c3eb6c2f501c8e6eb86a2461699..13186496728b81a091aab9bd83953cd09a0a3c21 100644 (file)
@@ -6,6 +6,7 @@
 
 #include <net/mac80211.h>
 #include <linux/etherdevice.h>
+
 #include "mac.h"
 #include "core.h"
 #include "debug.h"
index b1f343efa4b0d7d575ab82f3895a13b352c0e17f..8b80d5b6bc0160b9f62eaf4120432b56111c188d 100644 (file)
@@ -7783,3 +7783,151 @@ int ath12k_wmi_wow_config_pno(struct ath12k *ar, u32 vdev_id,
 
        return ath12k_wmi_cmd_send(ar->wmi, skb, WMI_NETWORK_LIST_OFFLOAD_CONFIG_CMDID);
 }
+
+static void ath12k_wmi_fill_ns_offload(struct ath12k *ar,
+                                      struct wmi_arp_ns_offload_arg *offload,
+                                      void **ptr,
+                                      bool enable,
+                                      bool ext)
+{
+       struct wmi_ns_offload_params *ns;
+       struct wmi_tlv *tlv;
+       void *buf_ptr = *ptr;
+       u32 ns_cnt, ns_ext_tuples;
+       int i, max_offloads;
+
+       ns_cnt = offload->ipv6_count;
+
+       tlv  = buf_ptr;
+
+       if (ext) {
+               ns_ext_tuples = offload->ipv6_count - WMI_MAX_NS_OFFLOADS;
+               tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_STRUCT,
+                                                ns_ext_tuples * sizeof(*ns));
+               i = WMI_MAX_NS_OFFLOADS;
+               max_offloads = offload->ipv6_count;
+       } else {
+               tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_STRUCT,
+                                                WMI_MAX_NS_OFFLOADS * sizeof(*ns));
+               i = 0;
+               max_offloads = WMI_MAX_NS_OFFLOADS;
+       }
+
+       buf_ptr += sizeof(*tlv);
+
+       for (; i < max_offloads; i++) {
+               ns = buf_ptr;
+               ns->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_NS_OFFLOAD_TUPLE,
+                                                       sizeof(*ns));
+
+               if (enable) {
+                       if (i < ns_cnt)
+                               ns->flags |= cpu_to_le32(WMI_NSOL_FLAGS_VALID);
+
+                       memcpy(ns->target_ipaddr[0], offload->ipv6_addr[i], 16);
+                       memcpy(ns->solicitation_ipaddr, offload->self_ipv6_addr[i], 16);
+
+                       if (offload->ipv6_type[i])
+                               ns->flags |= cpu_to_le32(WMI_NSOL_FLAGS_IS_IPV6_ANYCAST);
+
+                       memcpy(ns->target_mac.addr, offload->mac_addr, ETH_ALEN);
+
+                       if (!is_zero_ether_addr(ns->target_mac.addr))
+                               ns->flags |= cpu_to_le32(WMI_NSOL_FLAGS_MAC_VALID);
+
+                       ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
+                                  "wmi index %d ns_solicited %pI6 target %pI6",
+                                  i, ns->solicitation_ipaddr,
+                                  ns->target_ipaddr[0]);
+               }
+
+               buf_ptr += sizeof(*ns);
+       }
+
+       *ptr = buf_ptr;
+}
+
+static void ath12k_wmi_fill_arp_offload(struct ath12k *ar,
+                                       struct wmi_arp_ns_offload_arg *offload,
+                                       void **ptr,
+                                       bool enable)
+{
+       struct wmi_arp_offload_params *arp;
+       struct wmi_tlv *tlv;
+       void *buf_ptr = *ptr;
+       int i;
+
+       /* fill arp tuple */
+       tlv = buf_ptr;
+       tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_STRUCT,
+                                        WMI_MAX_ARP_OFFLOADS * sizeof(*arp));
+       buf_ptr += sizeof(*tlv);
+
+       for (i = 0; i < WMI_MAX_ARP_OFFLOADS; i++) {
+               arp = buf_ptr;
+               arp->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_ARP_OFFLOAD_TUPLE,
+                                                        sizeof(*arp));
+
+               if (enable && i < offload->ipv4_count) {
+                       /* Copy the target ip addr and flags */
+                       arp->flags = cpu_to_le32(WMI_ARPOL_FLAGS_VALID);
+                       memcpy(arp->target_ipaddr, offload->ipv4_addr[i], 4);
+
+                       ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "wmi arp offload address %pI4",
+                                  arp->target_ipaddr);
+               }
+
+               buf_ptr += sizeof(*arp);
+       }
+
+       *ptr = buf_ptr;
+}
+
+int ath12k_wmi_arp_ns_offload(struct ath12k *ar,
+                             struct ath12k_vif *arvif,
+                             struct wmi_arp_ns_offload_arg *offload,
+                             bool enable)
+{
+       struct wmi_set_arp_ns_offload_cmd *cmd;
+       struct wmi_tlv *tlv;
+       struct sk_buff *skb;
+       void *buf_ptr;
+       size_t len;
+       u8 ns_cnt, ns_ext_tuples = 0;
+
+       ns_cnt = offload->ipv6_count;
+
+       len = sizeof(*cmd) +
+             sizeof(*tlv) +
+             WMI_MAX_NS_OFFLOADS * sizeof(struct wmi_ns_offload_params) +
+             sizeof(*tlv) +
+             WMI_MAX_ARP_OFFLOADS * sizeof(struct wmi_arp_offload_params);
+
+       if (ns_cnt > WMI_MAX_NS_OFFLOADS) {
+               ns_ext_tuples = ns_cnt - WMI_MAX_NS_OFFLOADS;
+               len += sizeof(*tlv) +
+                      ns_ext_tuples * sizeof(struct wmi_ns_offload_params);
+       }
+
+       skb = ath12k_wmi_alloc_skb(ar->wmi->wmi_ab, len);
+       if (!skb)
+               return -ENOMEM;
+
+       buf_ptr = skb->data;
+       cmd = buf_ptr;
+       cmd->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_SET_ARP_NS_OFFLOAD_CMD,
+                                                sizeof(*cmd));
+       cmd->flags = cpu_to_le32(0);
+       cmd->vdev_id = cpu_to_le32(arvif->vdev_id);
+       cmd->num_ns_ext_tuples = cpu_to_le32(ns_ext_tuples);
+
+       buf_ptr += sizeof(*cmd);
+
+       ath12k_wmi_fill_ns_offload(ar, offload, &buf_ptr, enable, 0);
+       ath12k_wmi_fill_arp_offload(ar, offload, &buf_ptr, enable);
+
+       if (ns_ext_tuples)
+               ath12k_wmi_fill_ns_offload(ar, offload, &buf_ptr, enable, 1);
+
+       return ath12k_wmi_cmd_send(ar->wmi, skb, WMI_SET_ARP_NS_OFFLOAD_CMDID);
+}
index 62d7bb9e791d91e857d7c3d2f394964724894849..ac0517f46b726f253d8f86420dfe433890ee1ab9 100644 (file)
@@ -24,6 +24,7 @@
 
 struct ath12k_base;
 struct ath12k;
+struct ath12k_vif;
 
 /* There is no signed version of __le32, so for a temporary solution come
  * up with our own version. The idea is from fs/ntfs/endian.h.
@@ -5313,6 +5314,66 @@ struct wmi_hw_data_filter_arg {
        u32 hw_filter_bitmap;
 };
 
+#define WMI_IPV6_UC_TYPE     0
+#define WMI_IPV6_AC_TYPE     1
+
+#define WMI_IPV6_MAX_COUNT   16
+#define WMI_IPV4_MAX_COUNT   2
+
+struct wmi_arp_ns_offload_arg {
+       u8  ipv4_addr[WMI_IPV4_MAX_COUNT][4];
+       u32 ipv4_count;
+       u32 ipv6_count;
+       u8  ipv6_addr[WMI_IPV6_MAX_COUNT][16];
+       u8  self_ipv6_addr[WMI_IPV6_MAX_COUNT][16];
+       u8  ipv6_type[WMI_IPV6_MAX_COUNT];
+       bool ipv6_valid[WMI_IPV6_MAX_COUNT];
+       u8  mac_addr[ETH_ALEN];
+};
+
+#define WMI_MAX_NS_OFFLOADS           2
+#define WMI_MAX_ARP_OFFLOADS          2
+
+#define WMI_ARPOL_FLAGS_VALID              BIT(0)
+#define WMI_ARPOL_FLAGS_MAC_VALID          BIT(1)
+#define WMI_ARPOL_FLAGS_REMOTE_IP_VALID    BIT(2)
+
+struct wmi_arp_offload_params {
+       __le32 tlv_header;
+       __le32 flags;
+       u8 target_ipaddr[4];
+       u8 remote_ipaddr[4];
+       struct ath12k_wmi_mac_addr_params target_mac;
+} __packed;
+
+#define WMI_NSOL_FLAGS_VALID               BIT(0)
+#define WMI_NSOL_FLAGS_MAC_VALID           BIT(1)
+#define WMI_NSOL_FLAGS_REMOTE_IP_VALID     BIT(2)
+#define WMI_NSOL_FLAGS_IS_IPV6_ANYCAST     BIT(3)
+
+#define WMI_NSOL_MAX_TARGET_IPS    2
+
+struct wmi_ns_offload_params {
+       __le32 tlv_header;
+       __le32 flags;
+       u8 target_ipaddr[WMI_NSOL_MAX_TARGET_IPS][16];
+       u8 solicitation_ipaddr[16];
+       u8 remote_ipaddr[16];
+       struct ath12k_wmi_mac_addr_params target_mac;
+} __packed;
+
+struct wmi_set_arp_ns_offload_cmd {
+       __le32 tlv_header;
+       __le32 flags;
+       __le32 vdev_id;
+       __le32 num_ns_ext_tuples;
+       /* The TLVs follow:
+        * wmi_ns_offload_params  ns[WMI_MAX_NS_OFFLOADS];
+        * wmi_arp_offload_params arp[WMI_MAX_ARP_OFFLOADS];
+        * wmi_ns_offload_params  ns_ext[num_ns_ext_tuples];
+        */
+} __packed;
+
 void ath12k_wmi_init_qcn9274(struct ath12k_base *ab,
                             struct ath12k_wmi_resource_config_arg *config);
 void ath12k_wmi_init_wcn7850(struct ath12k_base *ab,
@@ -5478,4 +5539,9 @@ int ath12k_wmi_wow_config_pno(struct ath12k *ar, u32 vdev_id,
                              struct wmi_pno_scan_req_arg  *pno_scan);
 int ath12k_wmi_hw_data_filter_cmd(struct ath12k *ar,
                                  struct wmi_hw_data_filter_arg *arg);
+int ath12k_wmi_arp_ns_offload(struct ath12k *ar,
+                             struct ath12k_vif *arvif,
+                             struct wmi_arp_ns_offload_arg *offload,
+                             bool enable);
+
 #endif
index 146281244f70084c7fc1a7a5576b6c03101b21dc..8dfa7853e7a4e21db7b78911929f3db4d039a25b 100644 (file)
@@ -5,6 +5,10 @@
  */
 
 #include <linux/delay.h>
+#include <linux/inetdevice.h>
+#include <net/addrconf.h>
+#include <net/if_inet6.h>
+#include <net/ipv6.h>
 
 #include "mac.h"
 
@@ -599,6 +603,179 @@ static int ath12k_wow_clear_hw_filter(struct ath12k *ar)
        return 0;
 }
 
+static void ath12k_wow_generate_ns_mc_addr(struct ath12k_base *ab,
+                                          struct wmi_arp_ns_offload_arg *offload)
+{
+       int i;
+
+       for (i = 0; i < offload->ipv6_count; i++) {
+               offload->self_ipv6_addr[i][0] = 0xff;
+               offload->self_ipv6_addr[i][1] = 0x02;
+               offload->self_ipv6_addr[i][11] = 0x01;
+               offload->self_ipv6_addr[i][12] = 0xff;
+               offload->self_ipv6_addr[i][13] =
+                                       offload->ipv6_addr[i][13];
+               offload->self_ipv6_addr[i][14] =
+                                       offload->ipv6_addr[i][14];
+               offload->self_ipv6_addr[i][15] =
+                                       offload->ipv6_addr[i][15];
+               ath12k_dbg(ab, ATH12K_DBG_WOW, "NS solicited addr %pI6\n",
+                          offload->self_ipv6_addr[i]);
+       }
+}
+
+static void ath12k_wow_prepare_ns_offload(struct ath12k_vif *arvif,
+                                         struct wmi_arp_ns_offload_arg *offload)
+{
+       struct net_device *ndev = ieee80211_vif_to_wdev(arvif->vif)->netdev;
+       struct ath12k_base *ab = arvif->ar->ab;
+       struct inet6_ifaddr *ifa6;
+       struct ifacaddr6 *ifaca6;
+       struct inet6_dev *idev;
+       u32 count = 0, scope;
+
+       if (!ndev)
+               return;
+
+       idev = in6_dev_get(ndev);
+       if (!idev)
+               return;
+
+       ath12k_dbg(ab, ATH12K_DBG_WOW, "wow prepare ns offload\n");
+
+       read_lock_bh(&idev->lock);
+
+       /* get unicast address */
+       list_for_each_entry(ifa6, &idev->addr_list, if_list) {
+               if (count >= WMI_IPV6_MAX_COUNT)
+                       goto unlock;
+
+               if (ifa6->flags & IFA_F_DADFAILED)
+                       continue;
+
+               scope = ipv6_addr_src_scope(&ifa6->addr);
+               if (scope != IPV6_ADDR_SCOPE_LINKLOCAL &&
+                   scope != IPV6_ADDR_SCOPE_GLOBAL) {
+                       ath12k_dbg(ab, ATH12K_DBG_WOW,
+                                  "Unsupported ipv6 scope: %d\n", scope);
+                       continue;
+               }
+
+               memcpy(offload->ipv6_addr[count], &ifa6->addr.s6_addr,
+                      sizeof(ifa6->addr.s6_addr));
+               offload->ipv6_type[count] = WMI_IPV6_UC_TYPE;
+               ath12k_dbg(ab, ATH12K_DBG_WOW, "mac count %d ipv6 uc %pI6 scope %d\n",
+                          count, offload->ipv6_addr[count],
+                          scope);
+               count++;
+       }
+
+       /* get anycast address */
+       rcu_read_lock();
+
+       for (ifaca6 = rcu_dereference(idev->ac_list); ifaca6;
+            ifaca6 = rcu_dereference(ifaca6->aca_next)) {
+               if (count >= WMI_IPV6_MAX_COUNT) {
+                       rcu_read_unlock();
+                       goto unlock;
+               }
+
+               scope = ipv6_addr_src_scope(&ifaca6->aca_addr);
+               if (scope != IPV6_ADDR_SCOPE_LINKLOCAL &&
+                   scope != IPV6_ADDR_SCOPE_GLOBAL) {
+                       ath12k_dbg(ab, ATH12K_DBG_WOW,
+                                  "Unsupported ipv scope: %d\n", scope);
+                       continue;
+               }
+
+               memcpy(offload->ipv6_addr[count], &ifaca6->aca_addr,
+                      sizeof(ifaca6->aca_addr));
+               offload->ipv6_type[count] = WMI_IPV6_AC_TYPE;
+               ath12k_dbg(ab, ATH12K_DBG_WOW, "mac count %d ipv6 ac %pI6 scope %d\n",
+                          count, offload->ipv6_addr[count],
+                          scope);
+               count++;
+       }
+
+       rcu_read_unlock();
+
+unlock:
+       read_unlock_bh(&idev->lock);
+
+       in6_dev_put(idev);
+
+       offload->ipv6_count = count;
+       ath12k_wow_generate_ns_mc_addr(ab, offload);
+}
+
+static void ath12k_wow_prepare_arp_offload(struct ath12k_vif *arvif,
+                                          struct wmi_arp_ns_offload_arg *offload)
+{
+       struct ieee80211_vif *vif = arvif->vif;
+       struct ieee80211_vif_cfg vif_cfg = vif->cfg;
+       struct ath12k_base *ab = arvif->ar->ab;
+       u32 ipv4_cnt;
+
+       ath12k_dbg(ab, ATH12K_DBG_WOW, "wow prepare arp offload\n");
+
+       ipv4_cnt = min(vif_cfg.arp_addr_cnt, WMI_IPV4_MAX_COUNT);
+       memcpy(offload->ipv4_addr, vif_cfg.arp_addr_list, ipv4_cnt * sizeof(u32));
+       offload->ipv4_count = ipv4_cnt;
+
+       ath12k_dbg(ab, ATH12K_DBG_WOW,
+                  "wow arp_addr_cnt %d vif->addr %pM, offload_addr %pI4\n",
+                  vif_cfg.arp_addr_cnt, vif->addr, offload->ipv4_addr);
+}
+
+static int ath12k_wow_arp_ns_offload(struct ath12k *ar, bool enable)
+{
+       struct wmi_arp_ns_offload_arg *offload;
+       struct ath12k_vif *arvif;
+       int ret;
+
+       lockdep_assert_held(&ar->conf_mutex);
+
+       offload = kmalloc(sizeof(*offload), GFP_KERNEL);
+       if (!offload)
+               return -ENOMEM;
+
+       list_for_each_entry(arvif, &ar->arvifs, list) {
+               if (arvif->vdev_type != WMI_VDEV_TYPE_STA)
+                       continue;
+
+               memset(offload, 0, sizeof(*offload));
+
+               memcpy(offload->mac_addr, arvif->vif->addr, ETH_ALEN);
+               ath12k_wow_prepare_ns_offload(arvif, offload);
+               ath12k_wow_prepare_arp_offload(arvif, offload);
+
+               ret = ath12k_wmi_arp_ns_offload(ar, arvif, offload, enable);
+               if (ret) {
+                       ath12k_warn(ar->ab, "failed to set arp ns offload vdev %i: enable %d, ret %d\n",
+                                   arvif->vdev_id, enable, ret);
+                       return ret;
+               }
+       }
+
+       kfree(offload);
+
+       return 0;
+}
+
+static int ath12k_wow_protocol_offload(struct ath12k *ar, bool enable)
+{
+       int ret;
+
+       ret = ath12k_wow_arp_ns_offload(ar, enable);
+       if (ret) {
+               ath12k_warn(ar->ab, "failed to offload ARP and NS %d %d\n",
+                           enable, ret);
+               return ret;
+       }
+
+       return 0;
+}
+
 int ath12k_wow_op_suspend(struct ieee80211_hw *hw,
                          struct cfg80211_wowlan *wowlan)
 {
@@ -622,6 +799,13 @@ int ath12k_wow_op_suspend(struct ieee80211_hw *hw,
                goto cleanup;
        }
 
+       ret = ath12k_wow_protocol_offload(ar, true);
+       if (ret) {
+               ath12k_warn(ar->ab, "failed to set wow protocol offload events: %d\n",
+                           ret);
+               goto cleanup;
+       }
+
        ret = ath12k_mac_wait_tx_complete(ar);
        if (ret) {
                ath12k_warn(ar->ab, "failed to wait tx complete: %d\n", ret);
@@ -708,6 +892,13 @@ int ath12k_wow_op_resume(struct ieee80211_hw *hw)
                goto exit;
        }
 
+       ret = ath12k_wow_protocol_offload(ar, false);
+       if (ret) {
+               ath12k_warn(ar->ab, "failed to clear wow protocol offload events: %d\n",
+                           ret);
+               goto exit;
+       }
+
 exit:
        if (ret) {
                switch (ah->state) {