// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2020 The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/delay.h>
#include <linux/inetdevice.h>
#include <net/addrconf.h>
#include <net/if_inet6.h>
#include <net/ipv6.h>
#include "mac.h"
#include <net/mac80211.h>
#include "core.h"
#include "hif.h"
#include "debug.h"
#include "wmi.h"
#include "wow.h"
static const struct wiphy_wowlan_support ath12k_wowlan_support = {
.flags = WIPHY_WOWLAN_DISCONNECT |
WIPHY_WOWLAN_MAGIC_PKT |
WIPHY_WOWLAN_SUPPORTS_GTK_REKEY |
WIPHY_WOWLAN_GTK_REKEY_FAILURE,
.pattern_min_len = WOW_MIN_PATTERN_SIZE,
.pattern_max_len = WOW_MAX_PATTERN_SIZE,
.max_pkt_offset = WOW_MAX_PKT_OFFSET,
};
static inline bool ath12k_wow_is_p2p_vdev(struct ath12k_vif *arvif)
{
return (arvif->vdev_subtype == WMI_VDEV_SUBTYPE_P2P_DEVICE ||
arvif->vdev_subtype == WMI_VDEV_SUBTYPE_P2P_CLIENT ||
arvif->vdev_subtype == WMI_VDEV_SUBTYPE_P2P_GO);
}
int ath12k_wow_enable(struct ath12k *ar)
{
struct ath12k_base *ab = ar->ab;
int i, ret;
clear_bit(ATH12K_FLAG_HTC_SUSPEND_COMPLETE, &ab->dev_flags);
/* The firmware might be busy and it can not enter WoW immediately.
* In that case firmware notifies host with
* ATH12K_HTC_MSG_NACK_SUSPEND message, asking host to try again
* later. Per the firmware team there could be up to 10 loops.
*/
for (i = 0; i < ATH12K_WOW_RETRY_NUM; i++) {
reinit_completion(&ab->htc_suspend);
ret = ath12k_wmi_wow_enable(ar);
if (ret) {
ath12k_warn(ab, "failed to issue wow enable: %d\n", ret);
return ret;
}
ret = wait_for_completion_timeout(&ab->htc_suspend, 3 * HZ);
if (ret == 0) {
ath12k_warn(ab,
"timed out while waiting for htc suspend completion\n");
return -ETIMEDOUT;
}
if (test_bit(ATH12K_FLAG_HTC_SUSPEND_COMPLETE, &ab->dev_flags))
/* success, suspend complete received */
return 0;
ath12k_warn(ab, "htc suspend not complete, retrying (try %d)\n",
i);
msleep(ATH12K_WOW_RETRY_WAIT_MS);
}
ath12k_warn(ab, "htc suspend not complete, failing after %d tries\n", i);
return -ETIMEDOUT;
}
int ath12k_wow_wakeup(struct ath12k *ar)
{
struct ath12k_base *ab = ar->ab;
int ret;
reinit_completion(&ab->wow.wakeup_completed);
ret = ath12k_wmi_wow_host_wakeup_ind(ar);
if (ret) {
ath12k_warn(ab, "failed to send wow wakeup indication: %d\n",
ret);
return ret;
}
ret = wait_for_completion_timeout(&ab->wow.wakeup_completed, 3 * HZ);
if (ret == 0) {
ath12k_warn(ab, "timed out while waiting for wow wakeup completion\n");
return -ETIMEDOUT;
}
return 0;
}
static int ath12k_wow_vif_cleanup(struct ath12k_vif *arvif)
{
struct ath12k *ar = arvif->ar;
int i, ret;
for (i = 0; i < WOW_EVENT_MAX; i++) {
ret = ath12k_wmi_wow_add_wakeup_event(ar, arvif->vdev_id, i, 0);
if (ret) {
ath12k_warn(ar->ab, "failed to issue wow wakeup for event %s on vdev %i: %d\n",
wow_wakeup_event(i), arvif->vdev_id, ret);
return ret;
}
}
for (i = 0; i < ar->wow.max_num_patterns; i++) {
ret = ath12k_wmi_wow_del_pattern(ar, arvif->vdev_id, i);
if (ret) {
ath12k_warn(ar->ab, "failed to delete wow pattern %d for vdev %i: %d\n",
i, arvif->vdev_id, ret);
return ret;
}
}
return 0;
}
static int ath12k_wow_cleanup(struct ath12k *ar)
{
struct ath12k_vif *arvif;
int ret;
lockdep_assert_held(&ar->conf_mutex);
list_for_each_entry(arvif, &ar->arvifs, list) {
ret = ath12k_wow_vif_cleanup(arvif);
if (ret) {
ath12k_warn(ar->ab, "failed to clean wow wakeups on vdev %i: %d\n",
arvif->vdev_id, ret);
return ret;
}
}
return 0;
}
/* Convert a 802.3 format to a 802.11 format.
* +------------+-----------+--------+----------------+
* 802.3: |dest mac(6B)|src mac(6B)|type(2B)| body... |
* +------------+-----------+--------+----------------+
* |__ |_______ |____________ |________
* | | | |
* +--+------------+----+-----------+---------------+-----------+
* 802.11: |4B|dest mac(6B)| 6B |src mac(6B)| 8B |type(2B)| body... |
* +--+------------+----+-----------+---------------+-----------+
*/
static void
ath12k_wow_convert_8023_to_80211(struct ath12k *ar,
const struct cfg80211_pkt_pattern *eth_pattern,
struct ath12k_pkt_pattern *i80211_pattern)
{
size_t r1042_eth_ofs = offsetof(struct rfc1042_hdr, eth_type);
size_t a1_ofs = offsetof(struct ieee80211_hdr_3addr, addr1);
size_t a3_ofs = offsetof(struct ieee80211_hdr_3addr, addr3);
size_t i80211_hdr_len = sizeof(struct ieee80211_hdr_3addr);
size_t prot_ofs = offsetof(struct ethhdr, h_proto);
size_t src_ofs = offsetof(struct ethhdr, h_source);
u8 eth_bytemask[WOW_MAX_PATTERN_SIZE] = {};
const u8 *eth_pat = eth_pattern->pattern;
size_t eth_pat_len = eth_pattern->pattern_len;
size_t eth_pkt_ofs = eth_pattern->pkt_offset;
u8 *bytemask = i80211_pattern->bytemask;
u8 *pat = i80211_pattern->pattern