mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
wifi: ath12k: support ARP and NS offload
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
This commit is contained in:
committed by
Kalle Valo
parent
66a9448b1b
commit
1666108c74
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <net/mac80211.h>
|
||||
#include <linux/etherdevice.h>
|
||||
|
||||
#include "mac.h"
|
||||
#include "core.h"
|
||||
#include "debug.h"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user