mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

ieee80211_add_gtk_rekey receives a keyconf as an argument, and the cipher and keylen are taken from there to the new allocated key. But in rekey, both the cipher and the keylen should be the same as of the old key, so let ieee80211_add_gtk_rekey find those, so drivers won't have to fill it in. Reviewed-by: Johannes Berg <johannes.berg@intel.com> Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com> Link: https://patch.msgid.link/20250721214922.3c5c023bfae9.Ie6594ae2b4b6d5b3d536e642b349046ebfce7a5d@changeid Signed-off-by: Johannes Berg <johannes.berg@intel.com>
1906 lines
53 KiB
C
1906 lines
53 KiB
C
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
|
/*
|
|
* Copyright (C) 2024-2025 Intel Corporation
|
|
*/
|
|
#include "mld.h"
|
|
|
|
#include "d3.h"
|
|
#include "power.h"
|
|
#include "hcmd.h"
|
|
#include "iface.h"
|
|
#include "mcc.h"
|
|
#include "sta.h"
|
|
#include "mlo.h"
|
|
|
|
#include "fw/api/d3.h"
|
|
#include "fw/api/offload.h"
|
|
#include "fw/api/sta.h"
|
|
#include "fw/dbg.h"
|
|
|
|
#include <net/ipv6.h>
|
|
#include <net/addrconf.h>
|
|
#include <linux/bitops.h>
|
|
|
|
/**
|
|
* enum iwl_mld_d3_notif - d3 notifications
|
|
* @IWL_D3_NOTIF_WOWLAN_INFO: WOWLAN_INFO_NOTIF is expected/was received
|
|
* @IWL_D3_NOTIF_WOWLAN_WAKE_PKT: WOWLAN_WAKE_PKT_NOTIF is expected/was received
|
|
* @IWL_D3_NOTIF_PROT_OFFLOAD: PROT_OFFLOAD_NOTIF is expected/was received
|
|
* @IWL_D3_ND_MATCH_INFO: OFFLOAD_MATCH_INFO_NOTIF is expected/was received
|
|
* @IWL_D3_NOTIF_D3_END_NOTIF: D3_END_NOTIF is expected/was received
|
|
*/
|
|
enum iwl_mld_d3_notif {
|
|
IWL_D3_NOTIF_WOWLAN_INFO = BIT(0),
|
|
IWL_D3_NOTIF_WOWLAN_WAKE_PKT = BIT(1),
|
|
IWL_D3_NOTIF_PROT_OFFLOAD = BIT(2),
|
|
IWL_D3_ND_MATCH_INFO = BIT(3),
|
|
IWL_D3_NOTIF_D3_END_NOTIF = BIT(4)
|
|
};
|
|
|
|
struct iwl_mld_resume_key_iter_data {
|
|
struct iwl_mld *mld;
|
|
struct iwl_mld_wowlan_status *wowlan_status;
|
|
u32 num_keys, gtk_cipher, igtk_cipher, bigtk_cipher;
|
|
bool unhandled_cipher;
|
|
};
|
|
|
|
struct iwl_mld_suspend_key_iter_data {
|
|
struct iwl_wowlan_rsc_tsc_params_cmd *rsc;
|
|
bool have_rsc;
|
|
int gtks;
|
|
int found_gtk_idx[4];
|
|
__le32 gtk_cipher;
|
|
__le32 igtk_cipher;
|
|
__le32 bigtk_cipher;
|
|
};
|
|
|
|
struct iwl_mld_mcast_key_data {
|
|
u8 key[WOWLAN_KEY_MAX_SIZE];
|
|
u8 len;
|
|
u8 flags;
|
|
u8 id;
|
|
union {
|
|
struct {
|
|
struct ieee80211_key_seq aes_seq[IWL_MAX_TID_COUNT];
|
|
struct ieee80211_key_seq tkip_seq[IWL_MAX_TID_COUNT];
|
|
} gtk;
|
|
struct {
|
|
struct ieee80211_key_seq cmac_gmac_seq;
|
|
} igtk_bigtk;
|
|
};
|
|
|
|
};
|
|
|
|
/**
|
|
* struct iwl_mld_wowlan_status - contains wowlan status data from
|
|
* all wowlan notifications
|
|
* @wakeup_reasons: wakeup reasons, see &enum iwl_wowlan_wakeup_reason
|
|
* @replay_ctr: GTK rekey replay counter
|
|
* @pattern_number: number of the matched patterns on packets
|
|
* @last_qos_seq: QoS sequence counter of offloaded tid
|
|
* @num_of_gtk_rekeys: number of GTK rekeys during D3
|
|
* @tid_offloaded_tx: tid used by the firmware to transmit data packets
|
|
* while in wowlan
|
|
* @wake_packet: wakeup packet received
|
|
* @wake_packet_length: wake packet length
|
|
* @wake_packet_bufsize: wake packet bufsize
|
|
* @gtk: data of the last two used gtk's by the FW upon resume
|
|
* @igtk: data of the last used igtk by the FW upon resume
|
|
* @bigtk: data of the last two used gtk's by the FW upon resume
|
|
* @ptk: last seq numbers per tid passed by the FW,
|
|
* holds both in tkip and aes formats
|
|
*/
|
|
struct iwl_mld_wowlan_status {
|
|
u32 wakeup_reasons;
|
|
u64 replay_ctr;
|
|
u16 pattern_number;
|
|
u16 last_qos_seq;
|
|
u32 num_of_gtk_rekeys;
|
|
u8 tid_offloaded_tx;
|
|
u8 *wake_packet;
|
|
u32 wake_packet_length;
|
|
u32 wake_packet_bufsize;
|
|
struct iwl_mld_mcast_key_data gtk[WOWLAN_GTK_KEYS_NUM];
|
|
struct iwl_mld_mcast_key_data igtk;
|
|
struct iwl_mld_mcast_key_data bigtk[WOWLAN_BIGTK_KEYS_NUM];
|
|
struct {
|
|
struct ieee80211_key_seq aes_seq[IWL_MAX_TID_COUNT];
|
|
struct ieee80211_key_seq tkip_seq[IWL_MAX_TID_COUNT];
|
|
|
|
} ptk;
|
|
};
|
|
|
|
#define NETDETECT_QUERY_BUF_LEN \
|
|
(sizeof(struct iwl_scan_offload_profile_match) * \
|
|
IWL_SCAN_MAX_PROFILES_V2)
|
|
|
|
/**
|
|
* struct iwl_mld_netdetect_res - contains netdetect results from
|
|
* match_info_notif
|
|
* @matched_profiles: bitmap of matched profiles, referencing the
|
|
* matches passed in the scan offload request
|
|
* @matches: array of match information, one for each match
|
|
*/
|
|
struct iwl_mld_netdetect_res {
|
|
u32 matched_profiles;
|
|
u8 matches[NETDETECT_QUERY_BUF_LEN];
|
|
};
|
|
|
|
/**
|
|
* struct iwl_mld_resume_data - d3 resume flow data
|
|
* @notifs_expected: bitmap of expected notifications from fw,
|
|
* see &enum iwl_mld_d3_notif
|
|
* @notifs_received: bitmap of received notifications from fw,
|
|
* see &enum iwl_mld_d3_notif
|
|
* @d3_end_flags: bitmap of flags from d3_end_notif
|
|
* @notif_handling_err: error handling one of the resume notifications
|
|
* @wowlan_status: wowlan status data from all wowlan notifications
|
|
* @netdetect_res: contains netdetect results from match_info_notif
|
|
*/
|
|
struct iwl_mld_resume_data {
|
|
u32 notifs_expected;
|
|
u32 notifs_received;
|
|
u32 d3_end_flags;
|
|
bool notif_handling_err;
|
|
struct iwl_mld_wowlan_status *wowlan_status;
|
|
struct iwl_mld_netdetect_res *netdetect_res;
|
|
};
|
|
|
|
#define IWL_WOWLAN_WAKEUP_REASON_HAS_WAKEUP_PKT \
|
|
(IWL_WOWLAN_WAKEUP_BY_MAGIC_PACKET | \
|
|
IWL_WOWLAN_WAKEUP_BY_PATTERN | \
|
|
IWL_WAKEUP_BY_PATTERN_IPV4_TCP_SYN |\
|
|
IWL_WAKEUP_BY_PATTERN_IPV4_TCP_SYN_WILDCARD |\
|
|
IWL_WAKEUP_BY_PATTERN_IPV6_TCP_SYN |\
|
|
IWL_WAKEUP_BY_PATTERN_IPV6_TCP_SYN_WILDCARD)
|
|
|
|
#define IWL_WOWLAN_OFFLOAD_TID 0
|
|
|
|
void iwl_mld_set_rekey_data(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct cfg80211_gtk_rekey_data *data)
|
|
{
|
|
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
struct iwl_mld_wowlan_data *wowlan_data = &mld_vif->wowlan_data;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
wowlan_data->rekey_data.kek_len = data->kek_len;
|
|
wowlan_data->rekey_data.kck_len = data->kck_len;
|
|
memcpy(wowlan_data->rekey_data.kek, data->kek, data->kek_len);
|
|
memcpy(wowlan_data->rekey_data.kck, data->kck, data->kck_len);
|
|
wowlan_data->rekey_data.akm = data->akm & 0xFF;
|
|
wowlan_data->rekey_data.replay_ctr =
|
|
cpu_to_le64(be64_to_cpup((const __be64 *)data->replay_ctr));
|
|
wowlan_data->rekey_data.valid = true;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
void iwl_mld_ipv6_addr_change(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct inet6_dev *idev)
|
|
{
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
struct iwl_mld_wowlan_data *wowlan_data = &mld_vif->wowlan_data;
|
|
struct inet6_ifaddr *ifa;
|
|
int idx = 0;
|
|
|
|
memset(wowlan_data->tentative_addrs, 0,
|
|
sizeof(wowlan_data->tentative_addrs));
|
|
|
|
read_lock_bh(&idev->lock);
|
|
list_for_each_entry(ifa, &idev->addr_list, if_list) {
|
|
wowlan_data->target_ipv6_addrs[idx] = ifa->addr;
|
|
if (ifa->flags & IFA_F_TENTATIVE)
|
|
__set_bit(idx, wowlan_data->tentative_addrs);
|
|
idx++;
|
|
if (idx >= IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_MAX)
|
|
break;
|
|
}
|
|
read_unlock_bh(&idev->lock);
|
|
|
|
wowlan_data->num_target_ipv6_addrs = idx;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
iwl_mld_netdetect_config(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
const struct cfg80211_wowlan *wowlan)
|
|
{
|
|
int ret;
|
|
struct cfg80211_sched_scan_request *netdetect_cfg =
|
|
wowlan->nd_config;
|
|
struct ieee80211_scan_ies ies = {};
|
|
|
|
ret = iwl_mld_scan_stop(mld, IWL_MLD_SCAN_SCHED, true);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = iwl_mld_sched_scan_start(mld, vif, netdetect_cfg, &ies,
|
|
IWL_MLD_SCAN_NETDETECT);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
iwl_mld_le64_to_tkip_seq(__le64 le_pn, struct ieee80211_key_seq *seq)
|
|
{
|
|
u64 pn = le64_to_cpu(le_pn);
|
|
|
|
seq->tkip.iv16 = (u16)pn;
|
|
seq->tkip.iv32 = (u32)(pn >> 16);
|
|
}
|
|
|
|
static void
|
|
iwl_mld_le64_to_aes_seq(__le64 le_pn, struct ieee80211_key_seq *seq)
|
|
{
|
|
u64 pn = le64_to_cpu(le_pn);
|
|
|
|
seq->ccmp.pn[0] = pn >> 40;
|
|
seq->ccmp.pn[1] = pn >> 32;
|
|
seq->ccmp.pn[2] = pn >> 24;
|
|
seq->ccmp.pn[3] = pn >> 16;
|
|
seq->ccmp.pn[4] = pn >> 8;
|
|
seq->ccmp.pn[5] = pn;
|
|
}
|
|
|
|
static void
|
|
iwl_mld_convert_gtk_resume_seq(struct iwl_mld_mcast_key_data *gtk_data,
|
|
const struct iwl_wowlan_all_rsc_tsc_v5 *sc,
|
|
int rsc_idx)
|
|
{
|
|
struct ieee80211_key_seq *aes_seq = gtk_data->gtk.aes_seq;
|
|
struct ieee80211_key_seq *tkip_seq = gtk_data->gtk.tkip_seq;
|
|
|
|
if (rsc_idx >= ARRAY_SIZE(sc->mcast_rsc))
|
|
return;
|
|
|
|
/* We store both the TKIP and AES representations coming from the
|
|
* FW because we decode the data from there before we iterate
|
|
* the keys and know which type is used.
|
|
*/
|
|
for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) {
|
|
iwl_mld_le64_to_tkip_seq(sc->mcast_rsc[rsc_idx][tid],
|
|
&tkip_seq[tid]);
|
|
iwl_mld_le64_to_aes_seq(sc->mcast_rsc[rsc_idx][tid],
|
|
&aes_seq[tid]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
iwl_mld_convert_gtk_resume_data(struct iwl_mld *mld,
|
|
struct iwl_mld_wowlan_status *wowlan_status,
|
|
const struct iwl_wowlan_gtk_status_v3 *gtk_data,
|
|
const struct iwl_wowlan_all_rsc_tsc_v5 *sc)
|
|
{
|
|
int status_idx = 0;
|
|
|
|
BUILD_BUG_ON(sizeof(wowlan_status->gtk[0].key) <
|
|
sizeof(gtk_data[0].key));
|
|
BUILD_BUG_ON(ARRAY_SIZE(wowlan_status->gtk) < WOWLAN_GTK_KEYS_NUM);
|
|
|
|
for (int notif_idx = 0; notif_idx < ARRAY_SIZE(wowlan_status->gtk);
|
|
notif_idx++) {
|
|
int rsc_idx;
|
|
|
|
if (!(gtk_data[notif_idx].key_len))
|
|
continue;
|
|
|
|
wowlan_status->gtk[status_idx].len =
|
|
gtk_data[notif_idx].key_len;
|
|
wowlan_status->gtk[status_idx].flags =
|
|
gtk_data[notif_idx].key_flags;
|
|
wowlan_status->gtk[status_idx].id =
|
|
wowlan_status->gtk[status_idx].flags &
|
|
IWL_WOWLAN_GTK_IDX_MASK;
|
|
memcpy(wowlan_status->gtk[status_idx].key,
|
|
gtk_data[notif_idx].key,
|
|
sizeof(gtk_data[notif_idx].key));
|
|
|
|
/* The rsc for both gtk keys are stored in gtk[0]->sc->mcast_rsc
|
|
* The gtk ids can be any two numbers between 0 and 3,
|
|
* the id_map maps between the key id and the index in sc->mcast
|
|
*/
|
|
rsc_idx =
|
|
sc->mcast_key_id_map[wowlan_status->gtk[status_idx].id];
|
|
iwl_mld_convert_gtk_resume_seq(&wowlan_status->gtk[status_idx],
|
|
sc, rsc_idx);
|
|
|
|
/* if it's as long as the TKIP encryption key, copy MIC key */
|
|
if (wowlan_status->gtk[status_idx].len ==
|
|
NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY)
|
|
memcpy(wowlan_status->gtk[status_idx].key +
|
|
NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY,
|
|
gtk_data[notif_idx].tkip_mic_key,
|
|
sizeof(gtk_data[notif_idx].tkip_mic_key));
|
|
status_idx++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
iwl_mld_convert_ptk_resume_seq(struct iwl_mld *mld,
|
|
struct iwl_mld_wowlan_status *wowlan_status,
|
|
const struct iwl_wowlan_all_rsc_tsc_v5 *sc)
|
|
{
|
|
struct ieee80211_key_seq *aes_seq = wowlan_status->ptk.aes_seq;
|
|
struct ieee80211_key_seq *tkip_seq = wowlan_status->ptk.tkip_seq;
|
|
|
|
BUILD_BUG_ON(ARRAY_SIZE(sc->ucast_rsc) != IWL_MAX_TID_COUNT);
|
|
|
|
for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) {
|
|
iwl_mld_le64_to_aes_seq(sc->ucast_rsc[tid], &aes_seq[tid]);
|
|
iwl_mld_le64_to_tkip_seq(sc->ucast_rsc[tid], &tkip_seq[tid]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
iwl_mld_convert_mcast_ipn(struct iwl_mld_mcast_key_data *key_status,
|
|
const struct iwl_wowlan_igtk_status *key)
|
|
{
|
|
struct ieee80211_key_seq *seq =
|
|
&key_status->igtk_bigtk.cmac_gmac_seq;
|
|
u8 ipn_len = ARRAY_SIZE(key->ipn);
|
|
|
|
BUILD_BUG_ON(ipn_len != ARRAY_SIZE(seq->aes_gmac.pn));
|
|
BUILD_BUG_ON(ipn_len != ARRAY_SIZE(seq->aes_cmac.pn));
|
|
BUILD_BUG_ON(offsetof(struct ieee80211_key_seq, aes_gmac) !=
|
|
offsetof(struct ieee80211_key_seq, aes_cmac));
|
|
|
|
/* mac80211 expects big endian for memcmp() to work, convert.
|
|
* We don't have the key cipher yet so copy to both to cmac and gmac
|
|
*/
|
|
for (int i = 0; i < ipn_len; i++) {
|
|
seq->aes_gmac.pn[i] = key->ipn[ipn_len - i - 1];
|
|
seq->aes_cmac.pn[i] = key->ipn[ipn_len - i - 1];
|
|
}
|
|
}
|
|
|
|
static void
|
|
iwl_mld_convert_igtk_resume_data(struct iwl_mld_wowlan_status *wowlan_status,
|
|
const struct iwl_wowlan_igtk_status *igtk)
|
|
{
|
|
BUILD_BUG_ON(sizeof(wowlan_status->igtk.key) < sizeof(igtk->key));
|
|
|
|
if (!igtk->key_len)
|
|
return;
|
|
|
|
wowlan_status->igtk.len = igtk->key_len;
|
|
wowlan_status->igtk.flags = igtk->key_flags;
|
|
wowlan_status->igtk.id =
|
|
u32_get_bits(igtk->key_flags,
|
|
IWL_WOWLAN_IGTK_BIGTK_IDX_MASK) +
|
|
WOWLAN_IGTK_MIN_INDEX;
|
|
|
|
memcpy(wowlan_status->igtk.key, igtk->key, sizeof(igtk->key));
|
|
iwl_mld_convert_mcast_ipn(&wowlan_status->igtk, igtk);
|
|
}
|
|
|
|
static void
|
|
iwl_mld_convert_bigtk_resume_data(struct iwl_mld_wowlan_status *wowlan_status,
|
|
const struct iwl_wowlan_igtk_status *bigtk)
|
|
{
|
|
int status_idx = 0;
|
|
|
|
BUILD_BUG_ON(ARRAY_SIZE(wowlan_status->bigtk) < WOWLAN_BIGTK_KEYS_NUM);
|
|
|
|
for (int notif_idx = 0; notif_idx < WOWLAN_BIGTK_KEYS_NUM;
|
|
notif_idx++) {
|
|
if (!bigtk[notif_idx].key_len)
|
|
continue;
|
|
|
|
wowlan_status->bigtk[status_idx].len = bigtk[notif_idx].key_len;
|
|
wowlan_status->bigtk[status_idx].flags =
|
|
bigtk[notif_idx].key_flags;
|
|
wowlan_status->bigtk[status_idx].id =
|
|
u32_get_bits(bigtk[notif_idx].key_flags,
|
|
IWL_WOWLAN_IGTK_BIGTK_IDX_MASK)
|
|
+ WOWLAN_BIGTK_MIN_INDEX;
|
|
|
|
BUILD_BUG_ON(sizeof(wowlan_status->bigtk[status_idx].key) <
|
|
sizeof(bigtk[notif_idx].key));
|
|
memcpy(wowlan_status->bigtk[status_idx].key,
|
|
bigtk[notif_idx].key, sizeof(bigtk[notif_idx].key));
|
|
iwl_mld_convert_mcast_ipn(&wowlan_status->bigtk[status_idx],
|
|
&bigtk[notif_idx]);
|
|
status_idx++;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
iwl_mld_handle_wowlan_info_notif(struct iwl_mld *mld,
|
|
struct iwl_mld_wowlan_status *wowlan_status,
|
|
struct iwl_rx_packet *pkt)
|
|
{
|
|
const struct iwl_wowlan_info_notif *notif = (void *)pkt->data;
|
|
u32 expected_len, len = iwl_rx_packet_payload_len(pkt);
|
|
|
|
expected_len = sizeof(*notif);
|
|
|
|
if (IWL_FW_CHECK(mld, len < expected_len,
|
|
"Invalid wowlan_info_notif (expected=%ud got=%ud)\n",
|
|
expected_len, len))
|
|
return true;
|
|
|
|
if (IWL_FW_CHECK(mld, notif->tid_offloaded_tx != IWL_WOWLAN_OFFLOAD_TID,
|
|
"Invalid tid_offloaded_tx %d\n",
|
|
wowlan_status->tid_offloaded_tx))
|
|
return true;
|
|
|
|
iwl_mld_convert_gtk_resume_data(mld, wowlan_status, notif->gtk,
|
|
¬if->gtk[0].sc);
|
|
iwl_mld_convert_ptk_resume_seq(mld, wowlan_status, ¬if->gtk[0].sc);
|
|
/* only one igtk is passed by FW */
|
|
iwl_mld_convert_igtk_resume_data(wowlan_status, ¬if->igtk[0]);
|
|
iwl_mld_convert_bigtk_resume_data(wowlan_status, notif->bigtk);
|
|
|
|
wowlan_status->replay_ctr = le64_to_cpu(notif->replay_ctr);
|
|
wowlan_status->pattern_number = le16_to_cpu(notif->pattern_number);
|
|
|
|
wowlan_status->tid_offloaded_tx = notif->tid_offloaded_tx;
|
|
wowlan_status->last_qos_seq = le16_to_cpu(notif->qos_seq_ctr);
|
|
wowlan_status->num_of_gtk_rekeys =
|
|
le32_to_cpu(notif->num_of_gtk_rekeys);
|
|
wowlan_status->wakeup_reasons = le32_to_cpu(notif->wakeup_reasons);
|
|
return false;
|
|
/* TODO: mlo_links (task=MLO)*/
|
|
}
|
|
|
|
static bool
|
|
iwl_mld_handle_wake_pkt_notif(struct iwl_mld *mld,
|
|
struct iwl_mld_wowlan_status *wowlan_status,
|
|
struct iwl_rx_packet *pkt)
|
|
{
|
|
const struct iwl_wowlan_wake_pkt_notif *notif = (void *)pkt->data;
|
|
u32 actual_size, len = iwl_rx_packet_payload_len(pkt);
|
|
u32 expected_size = le32_to_cpu(notif->wake_packet_length);
|
|
|
|
if (IWL_FW_CHECK(mld, len < sizeof(*notif),
|
|
"Invalid WoWLAN wake packet notification (expected size=%zu got=%u)\n",
|
|
sizeof(*notif), len))
|
|
return true;
|
|
|
|
if (IWL_FW_CHECK(mld, !(wowlan_status->wakeup_reasons &
|
|
IWL_WOWLAN_WAKEUP_REASON_HAS_WAKEUP_PKT),
|
|
"Got wake packet but wakeup reason is %x\n",
|
|
wowlan_status->wakeup_reasons))
|
|
return true;
|
|
|
|
actual_size = len - offsetof(struct iwl_wowlan_wake_pkt_notif,
|
|
wake_packet);
|
|
|
|
/* actual_size got the padding from the notification, remove it. */
|
|
if (expected_size < actual_size)
|
|
actual_size = expected_size;
|
|
wowlan_status->wake_packet = kmemdup(notif->wake_packet, actual_size,
|
|
GFP_ATOMIC);
|
|
if (!wowlan_status->wake_packet)
|
|
return true;
|
|
|
|
wowlan_status->wake_packet_length = expected_size;
|
|
wowlan_status->wake_packet_bufsize = actual_size;
|
|
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
iwl_mld_set_wake_packet(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
const struct iwl_mld_wowlan_status *wowlan_status,
|
|
struct cfg80211_wowlan_wakeup *wakeup,
|
|
struct sk_buff **_pkt)
|
|
{
|
|
int pkt_bufsize = wowlan_status->wake_packet_bufsize;
|
|
int expected_pktlen = wowlan_status->wake_packet_length;
|
|
const u8 *pktdata = wowlan_status->wake_packet;
|
|
const struct ieee80211_hdr *hdr = (const void *)pktdata;
|
|
int truncated = expected_pktlen - pkt_bufsize;
|
|
|
|
if (ieee80211_is_data(hdr->frame_control)) {
|
|
int hdrlen = ieee80211_hdrlen(hdr->frame_control);
|
|
int ivlen = 0, icvlen = 4; /* also FCS */
|
|
|
|
struct sk_buff *pkt = alloc_skb(pkt_bufsize, GFP_KERNEL);
|
|
*_pkt = pkt;
|
|
if (!pkt)
|
|
return;
|
|
|
|
skb_put_data(pkt, pktdata, hdrlen);
|
|
pktdata += hdrlen;
|
|
pkt_bufsize -= hdrlen;
|
|
|
|
/* if truncated, FCS/ICV is (partially) gone */
|
|
if (truncated >= icvlen) {
|
|
truncated -= icvlen;
|
|
icvlen = 0;
|
|
} else {
|
|
icvlen -= truncated;
|
|
truncated = 0;
|
|
}
|
|
|
|
pkt_bufsize -= ivlen + icvlen;
|
|
pktdata += ivlen;
|
|
|
|
skb_put_data(pkt, pktdata, pkt_bufsize);
|
|
|
|
if (ieee80211_data_to_8023(pkt, vif->addr, vif->type))
|
|
return;
|
|
wakeup->packet = pkt->data;
|
|
wakeup->packet_present_len = pkt->len;
|
|
wakeup->packet_len = pkt->len - truncated;
|
|
wakeup->packet_80211 = false;
|
|
} else {
|
|
int fcslen = 4;
|
|
|
|
if (truncated >= 4) {
|
|
truncated -= 4;
|
|
fcslen = 0;
|
|
} else {
|
|
fcslen -= truncated;
|
|
truncated = 0;
|
|
}
|
|
pkt_bufsize -= fcslen;
|
|
wakeup->packet = wowlan_status->wake_packet;
|
|
wakeup->packet_present_len = pkt_bufsize;
|
|
wakeup->packet_len = expected_pktlen - truncated;
|
|
wakeup->packet_80211 = true;
|
|
}
|
|
}
|
|
|
|
static void
|
|
iwl_mld_report_wowlan_wakeup(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct iwl_mld_wowlan_status *wowlan_status)
|
|
{
|
|
struct sk_buff *pkt = NULL;
|
|
struct cfg80211_wowlan_wakeup wakeup = {
|
|
.pattern_idx = -1,
|
|
};
|
|
u32 reasons = wowlan_status->wakeup_reasons;
|
|
|
|
if (reasons == IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS) {
|
|
ieee80211_report_wowlan_wakeup(vif, NULL, GFP_KERNEL);
|
|
return;
|
|
}
|
|
|
|
pm_wakeup_event(mld->dev, 0);
|
|
|
|
if (reasons & IWL_WOWLAN_WAKEUP_BY_MAGIC_PACKET)
|
|
wakeup.magic_pkt = true;
|
|
|
|
if (reasons & IWL_WOWLAN_WAKEUP_BY_PATTERN)
|
|
wakeup.pattern_idx =
|
|
wowlan_status->pattern_number;
|
|
|
|
if (reasons & (IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON |
|
|
IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH |
|
|
IWL_WOWLAN_WAKEUP_BY_GTK_REKEY_FAILURE))
|
|
wakeup.disconnect = true;
|
|
|
|
if (reasons & IWL_WOWLAN_WAKEUP_BY_GTK_REKEY_FAILURE)
|
|
wakeup.gtk_rekey_failure = true;
|
|
|
|
if (reasons & IWL_WOWLAN_WAKEUP_BY_RFKILL_DEASSERTED)
|
|
wakeup.rfkill_release = true;
|
|
|
|
if (reasons & IWL_WOWLAN_WAKEUP_BY_EAPOL_REQUEST)
|
|
wakeup.eap_identity_req = true;
|
|
|
|
if (reasons & IWL_WOWLAN_WAKEUP_BY_FOUR_WAY_HANDSHAKE)
|
|
wakeup.four_way_handshake = true;
|
|
|
|
if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_LINK_LOSS)
|
|
wakeup.tcp_connlost = true;
|
|
|
|
if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_SIGNATURE_TABLE)
|
|
wakeup.tcp_nomoretokens = true;
|
|
|
|
if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_WAKEUP_PACKET)
|
|
wakeup.tcp_match = true;
|
|
|
|
if (reasons & IWL_WAKEUP_BY_11W_UNPROTECTED_DEAUTH_OR_DISASSOC)
|
|
wakeup.unprot_deauth_disassoc = true;
|
|
|
|
if (wowlan_status->wake_packet)
|
|
iwl_mld_set_wake_packet(mld, vif, wowlan_status, &wakeup, &pkt);
|
|
|
|
ieee80211_report_wowlan_wakeup(vif, &wakeup, GFP_KERNEL);
|
|
kfree_skb(pkt);
|
|
}
|
|
|
|
static void
|
|
iwl_mld_set_key_rx_seq_tids(struct ieee80211_key_conf *key,
|
|
struct ieee80211_key_seq *seq)
|
|
{
|
|
int tid;
|
|
|
|
for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++)
|
|
ieee80211_set_key_rx_seq(key, tid, &seq[tid]);
|
|
}
|
|
|
|
static void
|
|
iwl_mld_set_key_rx_seq(struct ieee80211_key_conf *key,
|
|
struct iwl_mld_mcast_key_data *key_data)
|
|
{
|
|
switch (key->cipher) {
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
case WLAN_CIPHER_SUITE_GCMP:
|
|
case WLAN_CIPHER_SUITE_GCMP_256:
|
|
iwl_mld_set_key_rx_seq_tids(key,
|
|
key_data->gtk.aes_seq);
|
|
break;
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
iwl_mld_set_key_rx_seq_tids(key,
|
|
key_data->gtk.tkip_seq);
|
|
break;
|
|
case WLAN_CIPHER_SUITE_BIP_GMAC_128:
|
|
case WLAN_CIPHER_SUITE_BIP_GMAC_256:
|
|
case WLAN_CIPHER_SUITE_BIP_CMAC_256:
|
|
case WLAN_CIPHER_SUITE_AES_CMAC:
|
|
/* igtk/bigtk ciphers*/
|
|
ieee80211_set_key_rx_seq(key, 0,
|
|
&key_data->igtk_bigtk.cmac_gmac_seq);
|
|
break;
|
|
default:
|
|
WARN_ON(1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
iwl_mld_update_ptk_rx_seq(struct iwl_mld *mld,
|
|
struct iwl_mld_wowlan_status *wowlan_status,
|
|
struct ieee80211_sta *sta,
|
|
struct ieee80211_key_conf *key,
|
|
bool is_tkip)
|
|
{
|
|
struct iwl_mld_sta *mld_sta =
|
|
iwl_mld_sta_from_mac80211(sta);
|
|
struct iwl_mld_ptk_pn *mld_ptk_pn =
|
|
wiphy_dereference(mld->wiphy,
|
|
mld_sta->ptk_pn[key->keyidx]);
|
|
|
|
iwl_mld_set_key_rx_seq_tids(key, is_tkip ?
|
|
wowlan_status->ptk.tkip_seq :
|
|
wowlan_status->ptk.aes_seq);
|
|
if (is_tkip)
|
|
return;
|
|
|
|
if (WARN_ON(!mld_ptk_pn))
|
|
return;
|
|
|
|
for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) {
|
|
for (int i = 1; i < mld->trans->info.num_rxqs; i++)
|
|
memcpy(mld_ptk_pn->q[i].pn[tid],
|
|
wowlan_status->ptk.aes_seq[tid].ccmp.pn,
|
|
IEEE80211_CCMP_PN_LEN);
|
|
}
|
|
}
|
|
|
|
static void
|
|
iwl_mld_resume_keys_iter(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_sta *sta,
|
|
struct ieee80211_key_conf *key,
|
|
void *_data)
|
|
{
|
|
struct iwl_mld_resume_key_iter_data *data = _data;
|
|
struct iwl_mld_wowlan_status *wowlan_status = data->wowlan_status;
|
|
u8 status_idx;
|
|
|
|
/* TODO: check key link id (task=MLO) */
|
|
if (data->unhandled_cipher)
|
|
return;
|
|
|
|
switch (key->cipher) {
|
|
case WLAN_CIPHER_SUITE_WEP40:
|
|
case WLAN_CIPHER_SUITE_WEP104:
|
|
/* ignore WEP completely, nothing to do */
|
|
return;
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
case WLAN_CIPHER_SUITE_GCMP:
|
|
case WLAN_CIPHER_SUITE_GCMP_256:
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
if (sta) {
|
|
iwl_mld_update_ptk_rx_seq(data->mld, wowlan_status,
|
|
sta, key,
|
|
key->cipher ==
|
|
WLAN_CIPHER_SUITE_TKIP);
|
|
return;
|
|
}
|
|
|
|
if (WARN_ON(data->gtk_cipher &&
|
|
data->gtk_cipher != key->cipher))
|
|
return;
|
|
|
|
data->gtk_cipher = key->cipher;
|
|
status_idx = key->keyidx == wowlan_status->gtk[1].id;
|
|
iwl_mld_set_key_rx_seq(key, &wowlan_status->gtk[status_idx]);
|
|
break;
|
|
case WLAN_CIPHER_SUITE_BIP_GMAC_128:
|
|
case WLAN_CIPHER_SUITE_BIP_GMAC_256:
|
|
case WLAN_CIPHER_SUITE_BIP_CMAC_256:
|
|
case WLAN_CIPHER_SUITE_AES_CMAC:
|
|
if (key->keyidx == 4 || key->keyidx == 5) {
|
|
if (WARN_ON(data->igtk_cipher &&
|
|
data->igtk_cipher != key->cipher))
|
|
return;
|
|
|
|
data->igtk_cipher = key->cipher;
|
|
if (key->keyidx == wowlan_status->igtk.id)
|
|
iwl_mld_set_key_rx_seq(key, &wowlan_status->igtk);
|
|
}
|
|
if (key->keyidx == 6 || key->keyidx == 7) {
|
|
if (WARN_ON(data->bigtk_cipher &&
|
|
data->bigtk_cipher != key->cipher))
|
|
return;
|
|
|
|
data->bigtk_cipher = key->cipher;
|
|
status_idx = key->keyidx == wowlan_status->bigtk[1].id;
|
|
iwl_mld_set_key_rx_seq(key, &wowlan_status->bigtk[status_idx]);
|
|
}
|
|
break;
|
|
default:
|
|
data->unhandled_cipher = true;
|
|
return;
|
|
}
|
|
data->num_keys++;
|
|
}
|
|
|
|
static void
|
|
iwl_mld_add_mcast_rekey(struct ieee80211_vif *vif,
|
|
struct iwl_mld *mld,
|
|
struct iwl_mld_mcast_key_data *key_data,
|
|
struct ieee80211_bss_conf *link_conf,
|
|
u32 cipher)
|
|
{
|
|
struct ieee80211_key_conf *key_config;
|
|
struct {
|
|
struct ieee80211_key_conf conf;
|
|
u8 key[WOWLAN_KEY_MAX_SIZE];
|
|
} conf = {
|
|
.conf.cipher = cipher,
|
|
.conf.keyidx = key_data->id,
|
|
};
|
|
int link_id = vif->active_links ? __ffs(vif->active_links) : -1;
|
|
u8 key[WOWLAN_KEY_MAX_SIZE];
|
|
|
|
BUILD_BUG_ON(WLAN_KEY_LEN_CCMP != WLAN_KEY_LEN_GCMP);
|
|
BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_CCMP);
|
|
BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_GCMP_256);
|
|
BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_TKIP);
|
|
BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_BIP_GMAC_128);
|
|
BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_BIP_GMAC_256);
|
|
BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_AES_CMAC);
|
|
BUILD_BUG_ON(sizeof(conf.key) < sizeof(key_data->key));
|
|
|
|
if (!key_data->len)
|
|
return;
|
|
|
|
switch (cipher) {
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
case WLAN_CIPHER_SUITE_GCMP:
|
|
conf.conf.keylen = WLAN_KEY_LEN_CCMP;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_GCMP_256:
|
|
conf.conf.keylen = WLAN_KEY_LEN_GCMP_256;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
conf.conf.keylen = WLAN_KEY_LEN_TKIP;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_BIP_GMAC_128:
|
|
conf.conf.keylen = WLAN_KEY_LEN_BIP_GMAC_128;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_BIP_GMAC_256:
|
|
conf.conf.keylen = WLAN_KEY_LEN_BIP_GMAC_256;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_AES_CMAC:
|
|
conf.conf.keylen = WLAN_KEY_LEN_AES_CMAC;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_BIP_CMAC_256:
|
|
conf.conf.keylen = WLAN_KEY_LEN_BIP_CMAC_256;
|
|
break;
|
|
default:
|
|
WARN_ON(1);
|
|
}
|
|
|
|
memcpy(conf.conf.key, key_data->key, conf.conf.keylen);
|
|
|
|
memcpy(key, key_data->key, sizeof(key_data->key));
|
|
|
|
key_config = ieee80211_gtk_rekey_add(vif, key_data->id, key,
|
|
sizeof(key), link_id);
|
|
if (IS_ERR(key_config))
|
|
return;
|
|
|
|
iwl_mld_set_key_rx_seq(key_config, key_data);
|
|
|
|
/* The FW holds only one igtk so we keep track of the valid one */
|
|
if (key_config->keyidx == 4 || key_config->keyidx == 5) {
|
|
struct iwl_mld_link *mld_link =
|
|
iwl_mld_link_from_mac80211(link_conf);
|
|
|
|
/* If we had more than one rekey, mac80211 will tell us to
|
|
* remove the old and add the new so we will update the IGTK in
|
|
* drv_set_key
|
|
*/
|
|
if (mld_link->igtk && mld_link->igtk != key_config) {
|
|
/* mark the old IGTK as not in FW */
|
|
mld_link->igtk->hw_key_idx = STA_KEY_IDX_INVALID;
|
|
mld_link->igtk = key_config;
|
|
}
|
|
}
|
|
|
|
/* Also keep track of the new BIGTK */
|
|
if ((key_config->keyidx == 6 || key_config->keyidx == 7) &&
|
|
vif->type == NL80211_IFTYPE_STATION) {
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
|
|
rcu_assign_pointer(mld_vif->bigtks[key_config->keyidx - 6], key_config);
|
|
}
|
|
}
|
|
|
|
static void
|
|
iwl_mld_add_all_rekeys(struct ieee80211_vif *vif,
|
|
struct iwl_mld_wowlan_status *wowlan_status,
|
|
struct iwl_mld_resume_key_iter_data *key_iter_data,
|
|
struct ieee80211_bss_conf *link_conf)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(wowlan_status->gtk); i++)
|
|
iwl_mld_add_mcast_rekey(vif, key_iter_data->mld,
|
|
&wowlan_status->gtk[i],
|
|
link_conf,
|
|
key_iter_data->gtk_cipher);
|
|
|
|
iwl_mld_add_mcast_rekey(vif, key_iter_data->mld,
|
|
&wowlan_status->igtk,
|
|
link_conf, key_iter_data->igtk_cipher);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(wowlan_status->bigtk); i++)
|
|
iwl_mld_add_mcast_rekey(vif, key_iter_data->mld,
|
|
&wowlan_status->bigtk[i],
|
|
link_conf,
|
|
key_iter_data->bigtk_cipher);
|
|
}
|
|
|
|
static bool
|
|
iwl_mld_update_sec_keys(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct iwl_mld_wowlan_status *wowlan_status)
|
|
{
|
|
int link_id = vif->active_links ? __ffs(vif->active_links) : 0;
|
|
struct ieee80211_bss_conf *link_conf =
|
|
link_conf_dereference_protected(vif, link_id);
|
|
__be64 replay_ctr = cpu_to_be64(wowlan_status->replay_ctr);
|
|
struct iwl_mld_resume_key_iter_data key_iter_data = {
|
|
.mld = mld,
|
|
.wowlan_status = wowlan_status,
|
|
};
|
|
|
|
if (WARN_ON(!link_conf))
|
|
return false;
|
|
|
|
ieee80211_iter_keys(mld->hw, vif, iwl_mld_resume_keys_iter,
|
|
&key_iter_data);
|
|
|
|
if (key_iter_data.unhandled_cipher)
|
|
return false;
|
|
|
|
IWL_DEBUG_WOWLAN(mld,
|
|
"Number of installed keys: %d, Number of rekeys: %d\n",
|
|
key_iter_data.num_keys,
|
|
wowlan_status->num_of_gtk_rekeys);
|
|
|
|
if (!key_iter_data.num_keys || !wowlan_status->num_of_gtk_rekeys)
|
|
return true;
|
|
|
|
iwl_mld_add_all_rekeys(vif, wowlan_status, &key_iter_data,
|
|
link_conf);
|
|
|
|
ieee80211_gtk_rekey_notify(vif, link_conf->bssid,
|
|
(void *)&replay_ctr, GFP_KERNEL);
|
|
/* TODO: MLO rekey (task=MLO) */
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
iwl_mld_process_wowlan_status(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct iwl_mld_wowlan_status *wowlan_status)
|
|
{
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
struct ieee80211_sta *ap_sta = mld_vif->ap_sta;
|
|
struct iwl_mld_txq *mld_txq;
|
|
|
|
iwl_mld_report_wowlan_wakeup(mld, vif, wowlan_status);
|
|
|
|
if (WARN_ON(!ap_sta))
|
|
return false;
|
|
|
|
mld_txq =
|
|
iwl_mld_txq_from_mac80211(ap_sta->txq[wowlan_status->tid_offloaded_tx]);
|
|
|
|
/* Update the pointers of the Tx queue that may have moved during
|
|
* suspend if the firmware sent frames.
|
|
* The firmware stores last-used value, we store next value.
|
|
*/
|
|
WARN_ON(!mld_txq->status.allocated);
|
|
iwl_trans_set_q_ptrs(mld->trans, mld_txq->fw_id,
|
|
(wowlan_status->last_qos_seq +
|
|
0x10) >> 4);
|
|
|
|
if (!iwl_mld_update_sec_keys(mld, vif, wowlan_status))
|
|
return false;
|
|
|
|
if (wowlan_status->wakeup_reasons &
|
|
(IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON |
|
|
IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH |
|
|
IWL_WOWLAN_WAKEUP_BY_GTK_REKEY_FAILURE))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
iwl_mld_netdetect_match_info_handler(struct iwl_mld *mld,
|
|
struct iwl_mld_resume_data *resume_data,
|
|
struct iwl_rx_packet *pkt)
|
|
{
|
|
struct iwl_mld_netdetect_res *results = resume_data->netdetect_res;
|
|
const struct iwl_scan_offload_match_info *notif = (void *)pkt->data;
|
|
u32 len = iwl_rx_packet_payload_len(pkt);
|
|
|
|
if (IWL_FW_CHECK(mld, !mld->netdetect,
|
|
"Got scan match info notif when mld->netdetect==%d\n",
|
|
mld->netdetect))
|
|
return true;
|
|
|
|
if (IWL_FW_CHECK(mld, len < sizeof(*notif),
|
|
"Invalid scan offload match notif of length: %d\n",
|
|
len))
|
|
return true;
|
|
|
|
if (IWL_FW_CHECK(mld, resume_data->wowlan_status->wakeup_reasons !=
|
|
IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS,
|
|
"Ignore scan match info: unexpected wakeup reason (expected=0x%x got=0x%x)\n",
|
|
IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS,
|
|
resume_data->wowlan_status->wakeup_reasons))
|
|
return true;
|
|
|
|
results->matched_profiles = le32_to_cpu(notif->matched_profiles);
|
|
IWL_DEBUG_WOWLAN(mld, "number of matched profiles=%u\n",
|
|
results->matched_profiles);
|
|
|
|
if (results->matched_profiles)
|
|
memcpy(results->matches, notif->matches,
|
|
NETDETECT_QUERY_BUF_LEN);
|
|
|
|
/* No scan should be active at this point */
|
|
mld->scan.status = 0;
|
|
memset(mld->scan.uid_status, 0, sizeof(mld->scan.uid_status));
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
iwl_mld_set_netdetect_info(struct iwl_mld *mld,
|
|
const struct cfg80211_sched_scan_request *netdetect_cfg,
|
|
struct cfg80211_wowlan_nd_info *netdetect_info,
|
|
struct iwl_mld_netdetect_res *netdetect_res,
|
|
unsigned long matched_profiles)
|
|
{
|
|
int i;
|
|
|
|
for_each_set_bit(i, &matched_profiles, netdetect_cfg->n_match_sets) {
|
|
struct cfg80211_wowlan_nd_match *match;
|
|
int idx, j, n_channels = 0;
|
|
struct iwl_scan_offload_profile_match *matches =
|
|
(void *)netdetect_res->matches;
|
|
|
|
for (int k = 0; k < SCAN_OFFLOAD_MATCHING_CHANNELS_LEN; k++)
|
|
n_channels +=
|
|
hweight8(matches[i].matching_channels[k]);
|
|
match = kzalloc(struct_size(match, channels, n_channels),
|
|
GFP_KERNEL);
|
|
if (!match)
|
|
return;
|
|
|
|
netdetect_info->matches[netdetect_info->n_matches] = match;
|
|
netdetect_info->n_matches++;
|
|
|
|
/* We inverted the order of the SSIDs in the scan
|
|
* request, so invert the index here.
|
|
*/
|
|
idx = netdetect_cfg->n_match_sets - i - 1;
|
|
match->ssid.ssid_len =
|
|
netdetect_cfg->match_sets[idx].ssid.ssid_len;
|
|
memcpy(match->ssid.ssid,
|
|
netdetect_cfg->match_sets[idx].ssid.ssid,
|
|
match->ssid.ssid_len);
|
|
|
|
if (netdetect_cfg->n_channels < n_channels)
|
|
continue;
|
|
|
|
for_each_set_bit(j,
|
|
(unsigned long *)&matches[i].matching_channels[0],
|
|
sizeof(matches[i].matching_channels)) {
|
|
match->channels[match->n_channels] =
|
|
netdetect_cfg->channels[j]->center_freq;
|
|
match->n_channels++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
iwl_mld_process_netdetect_res(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct iwl_mld_resume_data *resume_data)
|
|
{
|
|
struct cfg80211_wowlan_nd_info *netdetect_info = NULL;
|
|
const struct cfg80211_sched_scan_request *netdetect_cfg;
|
|
struct cfg80211_wowlan_wakeup wakeup = {
|
|
.pattern_idx = -1,
|
|
};
|
|
struct cfg80211_wowlan_wakeup *wakeup_report = &wakeup;
|
|
unsigned long matched_profiles;
|
|
u32 wakeup_reasons;
|
|
int n_matches;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
if (WARN_ON(!mld->wiphy->wowlan_config ||
|
|
!mld->wiphy->wowlan_config->nd_config)) {
|
|
IWL_DEBUG_WOWLAN(mld,
|
|
"Netdetect isn't configured on resume flow\n");
|
|
goto out;
|
|
}
|
|
|
|
netdetect_cfg = mld->wiphy->wowlan_config->nd_config;
|
|
wakeup_reasons = resume_data->wowlan_status->wakeup_reasons;
|
|
|
|
if (wakeup_reasons & IWL_WOWLAN_WAKEUP_BY_RFKILL_DEASSERTED)
|
|
wakeup.rfkill_release = true;
|
|
|
|
if (wakeup_reasons != IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS)
|
|
goto out;
|
|
|
|
if (!resume_data->netdetect_res->matched_profiles) {
|
|
IWL_DEBUG_WOWLAN(mld,
|
|
"Netdetect results aren't valid\n");
|
|
wakeup_report = NULL;
|
|
goto out;
|
|
}
|
|
|
|
matched_profiles = resume_data->netdetect_res->matched_profiles;
|
|
if (!netdetect_cfg->n_match_sets) {
|
|
IWL_DEBUG_WOWLAN(mld,
|
|
"No netdetect match sets are configured\n");
|
|
goto out;
|
|
}
|
|
n_matches = hweight_long(matched_profiles);
|
|
netdetect_info = kzalloc(struct_size(netdetect_info, matches,
|
|
n_matches), GFP_KERNEL);
|
|
if (netdetect_info)
|
|
iwl_mld_set_netdetect_info(mld, netdetect_cfg, netdetect_info,
|
|
resume_data->netdetect_res,
|
|
matched_profiles);
|
|
|
|
wakeup.net_detect = netdetect_info;
|
|
out:
|
|
ieee80211_report_wowlan_wakeup(vif, wakeup_report, GFP_KERNEL);
|
|
if (netdetect_info) {
|
|
for (int i = 0; i < netdetect_info->n_matches; i++)
|
|
kfree(netdetect_info->matches[i]);
|
|
kfree(netdetect_info);
|
|
}
|
|
}
|
|
|
|
static bool iwl_mld_handle_d3_notif(struct iwl_notif_wait_data *notif_wait,
|
|
struct iwl_rx_packet *pkt, void *data)
|
|
{
|
|
struct iwl_mld_resume_data *resume_data = data;
|
|
struct iwl_mld *mld =
|
|
container_of(notif_wait, struct iwl_mld, notif_wait);
|
|
|
|
switch (WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd)) {
|
|
case WIDE_ID(PROT_OFFLOAD_GROUP, WOWLAN_INFO_NOTIFICATION): {
|
|
if (resume_data->notifs_received & IWL_D3_NOTIF_WOWLAN_INFO) {
|
|
IWL_DEBUG_WOWLAN(mld,
|
|
"got additional wowlan_info notif\n");
|
|
break;
|
|
}
|
|
resume_data->notif_handling_err =
|
|
iwl_mld_handle_wowlan_info_notif(mld,
|
|
resume_data->wowlan_status,
|
|
pkt);
|
|
resume_data->notifs_received |= IWL_D3_NOTIF_WOWLAN_INFO;
|
|
|
|
if (resume_data->wowlan_status->wakeup_reasons &
|
|
IWL_WOWLAN_WAKEUP_REASON_HAS_WAKEUP_PKT)
|
|
resume_data->notifs_expected |=
|
|
IWL_D3_NOTIF_WOWLAN_WAKE_PKT;
|
|
break;
|
|
}
|
|
case WIDE_ID(PROT_OFFLOAD_GROUP, WOWLAN_WAKE_PKT_NOTIFICATION): {
|
|
if (resume_data->notifs_received &
|
|
IWL_D3_NOTIF_WOWLAN_WAKE_PKT) {
|
|
/* We shouldn't get two wake packet notifications */
|
|
IWL_DEBUG_WOWLAN(mld,
|
|
"Got additional wowlan wake packet notification\n");
|
|
break;
|
|
}
|
|
resume_data->notif_handling_err =
|
|
iwl_mld_handle_wake_pkt_notif(mld,
|
|
resume_data->wowlan_status,
|
|
pkt);
|
|
resume_data->notifs_received |= IWL_D3_NOTIF_WOWLAN_WAKE_PKT;
|
|
break;
|
|
}
|
|
case WIDE_ID(SCAN_GROUP, OFFLOAD_MATCH_INFO_NOTIF): {
|
|
if (resume_data->notifs_received & IWL_D3_ND_MATCH_INFO) {
|
|
IWL_ERR(mld,
|
|
"Got additional netdetect match info\n");
|
|
break;
|
|
}
|
|
|
|
resume_data->notif_handling_err =
|
|
iwl_mld_netdetect_match_info_handler(mld, resume_data,
|
|
pkt);
|
|
resume_data->notifs_received |= IWL_D3_ND_MATCH_INFO;
|
|
break;
|
|
}
|
|
case WIDE_ID(PROT_OFFLOAD_GROUP, D3_END_NOTIFICATION): {
|
|
struct iwl_d3_end_notif *notif = (void *)pkt->data;
|
|
|
|
resume_data->d3_end_flags = le32_to_cpu(notif->flags);
|
|
resume_data->notifs_received |= IWL_D3_NOTIF_D3_END_NOTIF;
|
|
break;
|
|
}
|
|
default:
|
|
WARN_ON(1);
|
|
}
|
|
|
|
return resume_data->notifs_received == resume_data->notifs_expected;
|
|
}
|
|
|
|
#define IWL_MLD_D3_NOTIF_TIMEOUT (HZ / 3)
|
|
|
|
static int iwl_mld_wait_d3_notif(struct iwl_mld *mld,
|
|
struct iwl_mld_resume_data *resume_data,
|
|
bool with_wowlan)
|
|
{
|
|
static const u16 wowlan_resume_notif[] = {
|
|
WIDE_ID(PROT_OFFLOAD_GROUP, WOWLAN_INFO_NOTIFICATION),
|
|
WIDE_ID(PROT_OFFLOAD_GROUP, WOWLAN_WAKE_PKT_NOTIFICATION),
|
|
WIDE_ID(SCAN_GROUP, OFFLOAD_MATCH_INFO_NOTIF),
|
|
WIDE_ID(PROT_OFFLOAD_GROUP, D3_END_NOTIFICATION)
|
|
};
|
|
static const u16 d3_resume_notif[] = {
|
|
WIDE_ID(PROT_OFFLOAD_GROUP, D3_END_NOTIFICATION)
|
|
};
|
|
struct iwl_notification_wait wait_d3_notif;
|
|
enum iwl_d3_status d3_status;
|
|
int ret;
|
|
|
|
if (with_wowlan)
|
|
iwl_init_notification_wait(&mld->notif_wait, &wait_d3_notif,
|
|
wowlan_resume_notif,
|
|
ARRAY_SIZE(wowlan_resume_notif),
|
|
iwl_mld_handle_d3_notif,
|
|
resume_data);
|
|
else
|
|
iwl_init_notification_wait(&mld->notif_wait, &wait_d3_notif,
|
|
d3_resume_notif,
|
|
ARRAY_SIZE(d3_resume_notif),
|
|
iwl_mld_handle_d3_notif,
|
|
resume_data);
|
|
|
|
ret = iwl_trans_d3_resume(mld->trans, &d3_status, false, false);
|
|
if (ret || d3_status != IWL_D3_STATUS_ALIVE) {
|
|
if (d3_status != IWL_D3_STATUS_ALIVE) {
|
|
IWL_INFO(mld, "Device was reset during suspend\n");
|
|
ret = -ENOENT;
|
|
} else {
|
|
IWL_ERR(mld, "Transport resume failed\n");
|
|
}
|
|
iwl_remove_notification(&mld->notif_wait, &wait_d3_notif);
|
|
return ret;
|
|
}
|
|
|
|
ret = iwl_wait_notification(&mld->notif_wait, &wait_d3_notif,
|
|
IWL_MLD_D3_NOTIF_TIMEOUT);
|
|
if (ret)
|
|
IWL_ERR(mld, "Couldn't get the d3 notif %d\n", ret);
|
|
|
|
if (resume_data->notif_handling_err)
|
|
ret = -EIO;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int iwl_mld_no_wowlan_suspend(struct iwl_mld *mld)
|
|
{
|
|
struct iwl_d3_manager_config d3_cfg_cmd_data = {};
|
|
int ret;
|
|
|
|
if (mld->debug_max_sleep) {
|
|
d3_cfg_cmd_data.wakeup_host_timer =
|
|
cpu_to_le32(mld->debug_max_sleep);
|
|
d3_cfg_cmd_data.wakeup_flags =
|
|
cpu_to_le32(IWL_WAKEUP_D3_HOST_TIMER);
|
|
}
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
IWL_DEBUG_WOWLAN(mld, "Starting the no wowlan suspend flow\n");
|
|
|
|
iwl_mld_low_latency_stop(mld);
|
|
|
|
/* This will happen if iwl_mld_supsend failed with FW error */
|
|
if (mld->trans->state == IWL_TRANS_NO_FW &&
|
|
test_bit(STATUS_FW_ERROR, &mld->trans->status))
|
|
return -ENODEV;
|
|
|
|
ret = iwl_mld_update_device_power(mld, true);
|
|
if (ret) {
|
|
IWL_ERR(mld,
|
|
"d3 suspend: couldn't send power_device %d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
ret = iwl_mld_send_cmd_pdu(mld, D3_CONFIG_CMD,
|
|
&d3_cfg_cmd_data);
|
|
if (ret) {
|
|
IWL_ERR(mld,
|
|
"d3 suspend: couldn't send D3_CONFIG_CMD %d\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
ret = iwl_trans_d3_suspend(mld->trans, false, false);
|
|
if (ret) {
|
|
IWL_ERR(mld, "d3 suspend: trans_d3_suspend failed %d\n", ret);
|
|
} else {
|
|
/* Async notification might send hcmds, which is not allowed in suspend */
|
|
iwl_mld_cancel_async_notifications(mld);
|
|
mld->fw_status.in_d3 = true;
|
|
}
|
|
|
|
out:
|
|
if (ret) {
|
|
mld->trans->state = IWL_TRANS_NO_FW;
|
|
set_bit(STATUS_FW_ERROR, &mld->trans->status);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int iwl_mld_no_wowlan_resume(struct iwl_mld *mld)
|
|
{
|
|
struct iwl_mld_resume_data resume_data = {
|
|
.notifs_expected =
|
|
IWL_D3_NOTIF_D3_END_NOTIF,
|
|
};
|
|
int ret;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
IWL_DEBUG_WOWLAN(mld, "Starting the no wowlan resume flow\n");
|
|
|
|
mld->fw_status.in_d3 = false;
|
|
iwl_fw_dbg_read_d3_debug_data(&mld->fwrt);
|
|
|
|
ret = iwl_mld_wait_d3_notif(mld, &resume_data, false);
|
|
|
|
if (!ret && (resume_data.d3_end_flags & IWL_D0I3_RESET_REQUIRE))
|
|
return -ENODEV;
|
|
|
|
if (ret) {
|
|
mld->trans->state = IWL_TRANS_NO_FW;
|
|
set_bit(STATUS_FW_ERROR, &mld->trans->status);
|
|
return ret;
|
|
}
|
|
iwl_mld_low_latency_restart(mld);
|
|
|
|
return iwl_mld_update_device_power(mld, false);
|
|
}
|
|
|
|
static void
|
|
iwl_mld_aes_seq_to_le64_pn(struct ieee80211_key_conf *key,
|
|
__le64 *key_rsc)
|
|
{
|
|
for (int i = 0; i < IWL_MAX_TID_COUNT; i++) {
|
|
struct ieee80211_key_seq seq;
|
|
u8 *pn = key->cipher == WLAN_CIPHER_SUITE_CCMP ? seq.ccmp.pn :
|
|
seq.gcmp.pn;
|
|
|
|
ieee80211_get_key_rx_seq(key, i, &seq);
|
|
key_rsc[i] = cpu_to_le64((u64)pn[5] |
|
|
((u64)pn[4] << 8) |
|
|
((u64)pn[3] << 16) |
|
|
((u64)pn[2] << 24) |
|
|
((u64)pn[1] << 32) |
|
|
((u64)pn[0] << 40));
|
|
}
|
|
}
|
|
|
|
static void
|
|
iwl_mld_suspend_set_ucast_pn(struct iwl_mld *mld, struct ieee80211_sta *sta,
|
|
struct ieee80211_key_conf *key, __le64 *key_rsc)
|
|
{
|
|
struct iwl_mld_sta *mld_sta =
|
|
iwl_mld_sta_from_mac80211(sta);
|
|
struct iwl_mld_ptk_pn *mld_ptk_pn;
|
|
|
|
if (WARN_ON(key->keyidx >= ARRAY_SIZE(mld_sta->ptk_pn)))
|
|
return;
|
|
|
|
mld_ptk_pn = wiphy_dereference(mld->wiphy,
|
|
mld_sta->ptk_pn[key->keyidx]);
|
|
if (WARN_ON(!mld_ptk_pn))
|
|
return;
|
|
|
|
for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) {
|
|
struct ieee80211_key_seq seq;
|
|
u8 *max_pn = seq.ccmp.pn;
|
|
|
|
/* get the PN from mac80211, used on the default queue */
|
|
ieee80211_get_key_rx_seq(key, tid, &seq);
|
|
|
|
/* and use the internal data for all queues */
|
|
for (int que = 1; que < mld->trans->info.num_rxqs; que++) {
|
|
u8 *cur_pn = mld_ptk_pn->q[que].pn[tid];
|
|
|
|
if (memcmp(max_pn, cur_pn, IEEE80211_CCMP_PN_LEN) < 0)
|
|
max_pn = cur_pn;
|
|
}
|
|
key_rsc[tid] = cpu_to_le64((u64)max_pn[5] |
|
|
((u64)max_pn[4] << 8) |
|
|
((u64)max_pn[3] << 16) |
|
|
((u64)max_pn[2] << 24) |
|
|
((u64)max_pn[1] << 32) |
|
|
((u64)max_pn[0] << 40));
|
|
}
|
|
}
|
|
|
|
static void
|
|
iwl_mld_suspend_convert_tkip_ipn(struct ieee80211_key_conf *key,
|
|
__le64 *rsc)
|
|
{
|
|
struct ieee80211_key_seq seq;
|
|
|
|
for (int i = 0; i < IWL_MAX_TID_COUNT; i++) {
|
|
ieee80211_get_key_rx_seq(key, i, &seq);
|
|
rsc[i] =
|
|
cpu_to_le64(((u64)seq.tkip.iv32 << 16) |
|
|
seq.tkip.iv16);
|
|
}
|
|
}
|
|
|
|
static void
|
|
iwl_mld_suspend_key_data_iter(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_sta *sta,
|
|
struct ieee80211_key_conf *key,
|
|
void *_data)
|
|
{
|
|
struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
|
|
struct iwl_mld_suspend_key_iter_data *data = _data;
|
|
__le64 *key_rsc;
|
|
__le32 cipher = 0;
|
|
|
|
switch (key->cipher) {
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
cipher = cpu_to_le32(STA_KEY_FLG_CCM);
|
|
fallthrough;
|
|
case WLAN_CIPHER_SUITE_GCMP:
|
|
case WLAN_CIPHER_SUITE_GCMP_256:
|
|
if (!cipher)
|
|
cipher = cpu_to_le32(STA_KEY_FLG_GCMP);
|
|
fallthrough;
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
if (!cipher)
|
|
cipher = cpu_to_le32(STA_KEY_FLG_TKIP);
|
|
if (sta) {
|
|
key_rsc = data->rsc->ucast_rsc;
|
|
if (key->cipher == WLAN_CIPHER_SUITE_TKIP)
|
|
iwl_mld_suspend_convert_tkip_ipn(key, key_rsc);
|
|
else
|
|
iwl_mld_suspend_set_ucast_pn(mld, sta, key,
|
|
key_rsc);
|
|
|
|
data->have_rsc = true;
|
|
return;
|
|
}
|
|
/* We're iterating from old to new, there're 4 possible
|
|
* gtk ids, and only the last two keys matter
|
|
*/
|
|
if (WARN_ON(data->gtks >=
|
|
ARRAY_SIZE(data->found_gtk_idx)))
|
|
return;
|
|
|
|
if (WARN_ON(key->keyidx >=
|
|
ARRAY_SIZE(data->rsc->mcast_key_id_map)))
|
|
return;
|
|
data->gtk_cipher = cipher;
|
|
data->found_gtk_idx[data->gtks] = key->keyidx;
|
|
key_rsc = data->rsc->mcast_rsc[data->gtks % 2];
|
|
data->rsc->mcast_key_id_map[key->keyidx] =
|
|
data->gtks % 2;
|
|
|
|
if (data->gtks >= 2) {
|
|
int prev = data->gtks % 2;
|
|
int prev_idx = data->found_gtk_idx[prev];
|
|
|
|
data->rsc->mcast_key_id_map[prev_idx] =
|
|
IWL_MCAST_KEY_MAP_INVALID;
|
|
}
|
|
|
|
if (key->cipher == WLAN_CIPHER_SUITE_TKIP)
|
|
iwl_mld_suspend_convert_tkip_ipn(key, key_rsc);
|
|
else
|
|
iwl_mld_aes_seq_to_le64_pn(key, key_rsc);
|
|
|
|
data->gtks++;
|
|
data->have_rsc = true;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_BIP_GMAC_128:
|
|
case WLAN_CIPHER_SUITE_BIP_GMAC_256:
|
|
cipher = cpu_to_le32(STA_KEY_FLG_GCMP);
|
|
fallthrough;
|
|
case WLAN_CIPHER_SUITE_BIP_CMAC_256:
|
|
case WLAN_CIPHER_SUITE_AES_CMAC:
|
|
if (!cipher)
|
|
cipher = cpu_to_le32(STA_KEY_FLG_CCM);
|
|
if (key->keyidx == 4 || key->keyidx == 5)
|
|
data->igtk_cipher = cipher;
|
|
|
|
if (key->keyidx == 6 || key->keyidx == 7)
|
|
data->bigtk_cipher = cipher;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int
|
|
iwl_mld_send_kek_kck_cmd(struct iwl_mld *mld,
|
|
struct iwl_mld_vif *mld_vif,
|
|
struct iwl_mld_suspend_key_iter_data data,
|
|
int ap_sta_id)
|
|
{
|
|
struct iwl_wowlan_kek_kck_material_cmd_v4 kek_kck_cmd = {};
|
|
struct iwl_mld_rekey_data *rekey_data =
|
|
&mld_vif->wowlan_data.rekey_data;
|
|
|
|
memcpy(kek_kck_cmd.kck, rekey_data->kck,
|
|
rekey_data->kck_len);
|
|
kek_kck_cmd.kck_len = cpu_to_le16(rekey_data->kck_len);
|
|
memcpy(kek_kck_cmd.kek, rekey_data->kek,
|
|
rekey_data->kek_len);
|
|
kek_kck_cmd.kek_len = cpu_to_le16(rekey_data->kek_len);
|
|
kek_kck_cmd.replay_ctr = rekey_data->replay_ctr;
|
|
kek_kck_cmd.akm = cpu_to_le32(rekey_data->akm);
|
|
kek_kck_cmd.sta_id = cpu_to_le32(ap_sta_id);
|
|
kek_kck_cmd.gtk_cipher = data.gtk_cipher;
|
|
kek_kck_cmd.igtk_cipher = data.igtk_cipher;
|
|
kek_kck_cmd.bigtk_cipher = data.bigtk_cipher;
|
|
|
|
IWL_DEBUG_WOWLAN(mld, "setting akm %d\n",
|
|
rekey_data->akm);
|
|
|
|
return iwl_mld_send_cmd_pdu(mld, WOWLAN_KEK_KCK_MATERIAL,
|
|
&kek_kck_cmd);
|
|
}
|
|
|
|
static int
|
|
iwl_mld_suspend_send_security_cmds(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
struct iwl_mld_vif *mld_vif,
|
|
int ap_sta_id)
|
|
{
|
|
struct iwl_mld_suspend_key_iter_data data = {};
|
|
int ret;
|
|
|
|
data.rsc = kzalloc(sizeof(*data.rsc), GFP_KERNEL);
|
|
if (!data.rsc)
|
|
return -ENOMEM;
|
|
|
|
memset(data.rsc->mcast_key_id_map, IWL_MCAST_KEY_MAP_INVALID,
|
|
ARRAY_SIZE(data.rsc->mcast_key_id_map));
|
|
|
|
data.rsc->sta_id = cpu_to_le32(ap_sta_id);
|
|
ieee80211_iter_keys(mld->hw, vif,
|
|
iwl_mld_suspend_key_data_iter,
|
|
&data);
|
|
|
|
if (data.have_rsc)
|
|
ret = iwl_mld_send_cmd_pdu(mld, WOWLAN_TSC_RSC_PARAM,
|
|
data.rsc);
|
|
else
|
|
ret = 0;
|
|
|
|
if (!ret && mld_vif->wowlan_data.rekey_data.valid)
|
|
ret = iwl_mld_send_kek_kck_cmd(mld, mld_vif, data, ap_sta_id);
|
|
|
|
kfree(data.rsc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
iwl_mld_set_wowlan_config_cmd(struct iwl_mld *mld,
|
|
struct cfg80211_wowlan *wowlan,
|
|
struct iwl_wowlan_config_cmd *wowlan_config_cmd,
|
|
struct ieee80211_sta *ap_sta)
|
|
{
|
|
wowlan_config_cmd->is_11n_connection =
|
|
ap_sta->deflink.ht_cap.ht_supported;
|
|
wowlan_config_cmd->flags = ENABLE_L3_FILTERING |
|
|
ENABLE_NBNS_FILTERING | ENABLE_DHCP_FILTERING;
|
|
|
|
if (ap_sta->mfp)
|
|
wowlan_config_cmd->flags |= IS_11W_ASSOC;
|
|
|
|
if (wowlan->disconnect)
|
|
wowlan_config_cmd->wakeup_filter |=
|
|
cpu_to_le32(IWL_WOWLAN_WAKEUP_BEACON_MISS |
|
|
IWL_WOWLAN_WAKEUP_LINK_CHANGE);
|
|
if (wowlan->magic_pkt)
|
|
wowlan_config_cmd->wakeup_filter |=
|
|
cpu_to_le32(IWL_WOWLAN_WAKEUP_MAGIC_PACKET);
|
|
if (wowlan->gtk_rekey_failure)
|
|
wowlan_config_cmd->wakeup_filter |=
|
|
cpu_to_le32(IWL_WOWLAN_WAKEUP_GTK_REKEY_FAIL);
|
|
if (wowlan->eap_identity_req)
|
|
wowlan_config_cmd->wakeup_filter |=
|
|
cpu_to_le32(IWL_WOWLAN_WAKEUP_EAP_IDENT_REQ);
|
|
if (wowlan->four_way_handshake)
|
|
wowlan_config_cmd->wakeup_filter |=
|
|
cpu_to_le32(IWL_WOWLAN_WAKEUP_4WAY_HANDSHAKE);
|
|
if (wowlan->n_patterns)
|
|
wowlan_config_cmd->wakeup_filter |=
|
|
cpu_to_le32(IWL_WOWLAN_WAKEUP_PATTERN_MATCH);
|
|
|
|
if (wowlan->rfkill_release)
|
|
wowlan_config_cmd->wakeup_filter |=
|
|
cpu_to_le32(IWL_WOWLAN_WAKEUP_RF_KILL_DEASSERT);
|
|
|
|
if (wowlan->any) {
|
|
wowlan_config_cmd->wakeup_filter |=
|
|
cpu_to_le32(IWL_WOWLAN_WAKEUP_BEACON_MISS |
|
|
IWL_WOWLAN_WAKEUP_LINK_CHANGE |
|
|
IWL_WOWLAN_WAKEUP_RX_FRAME |
|
|
IWL_WOWLAN_WAKEUP_BCN_FILTERING);
|
|
}
|
|
}
|
|
|
|
static int iwl_mld_send_patterns(struct iwl_mld *mld,
|
|
struct cfg80211_wowlan *wowlan,
|
|
int ap_sta_id)
|
|
{
|
|
struct iwl_wowlan_patterns_cmd *pattern_cmd;
|
|
struct iwl_host_cmd cmd = {
|
|
.id = WOWLAN_PATTERNS,
|
|
.dataflags[0] = IWL_HCMD_DFL_NOCOPY,
|
|
};
|
|
int ret;
|
|
|
|
if (!wowlan->n_patterns)
|
|
return 0;
|
|
|
|
cmd.len[0] = struct_size(pattern_cmd, patterns, wowlan->n_patterns);
|
|
|
|
pattern_cmd = kzalloc(cmd.len[0], GFP_KERNEL);
|
|
if (!pattern_cmd)
|
|
return -ENOMEM;
|
|
|
|
pattern_cmd->n_patterns = wowlan->n_patterns;
|
|
pattern_cmd->sta_id = ap_sta_id;
|
|
|
|
for (int i = 0; i < wowlan->n_patterns; i++) {
|
|
int mask_len = DIV_ROUND_UP(wowlan->patterns[i].pattern_len, 8);
|
|
|
|
pattern_cmd->patterns[i].pattern_type =
|
|
WOWLAN_PATTERN_TYPE_BITMASK;
|
|
|
|
memcpy(&pattern_cmd->patterns[i].u.bitmask.mask,
|
|
wowlan->patterns[i].mask, mask_len);
|
|
memcpy(&pattern_cmd->patterns[i].u.bitmask.pattern,
|
|
wowlan->patterns[i].pattern,
|
|
wowlan->patterns[i].pattern_len);
|
|
pattern_cmd->patterns[i].u.bitmask.mask_size = mask_len;
|
|
pattern_cmd->patterns[i].u.bitmask.pattern_size =
|
|
wowlan->patterns[i].pattern_len;
|
|
}
|
|
|
|
cmd.data[0] = pattern_cmd;
|
|
ret = iwl_mld_send_cmd(mld, &cmd);
|
|
kfree(pattern_cmd);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
iwl_mld_send_proto_offload(struct iwl_mld *mld,
|
|
struct ieee80211_vif *vif,
|
|
u8 ap_sta_id)
|
|
{
|
|
struct iwl_proto_offload_cmd_v4 *cmd __free(kfree);
|
|
struct iwl_host_cmd hcmd = {
|
|
.id = PROT_OFFLOAD_CONFIG_CMD,
|
|
.dataflags[0] = IWL_HCMD_DFL_NOCOPY,
|
|
.len[0] = sizeof(*cmd),
|
|
};
|
|
u32 enabled = 0;
|
|
|
|
cmd = kzalloc(hcmd.len[0], GFP_KERNEL);
|
|
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
|
|
struct iwl_mld_wowlan_data *wowlan_data = &mld_vif->wowlan_data;
|
|
struct iwl_ns_config *nsc;
|
|
struct iwl_targ_addr *addrs;
|
|
int n_nsc, n_addrs;
|
|
int i, c;
|
|
int num_skipped = 0;
|
|
|
|
nsc = cmd->ns_config;
|
|
n_nsc = IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3L;
|
|
addrs = cmd->targ_addrs;
|
|
n_addrs = IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3L;
|
|
|
|
/* For each address we have (and that will fit) fill a target
|
|
* address struct and combine for NS offload structs with the
|
|
* solicited node addresses.
|
|
*/
|
|
for (i = 0, c = 0;
|
|
i < wowlan_data->num_target_ipv6_addrs &&
|
|
i < n_addrs && c < n_nsc; i++) {
|
|
int j;
|
|
struct in6_addr solicited_addr;
|
|
|
|
/* Because ns is offloaded skip tentative address to avoid
|
|
* violating RFC4862.
|
|
*/
|
|
if (test_bit(i, wowlan_data->tentative_addrs)) {
|
|
num_skipped++;
|
|
continue;
|
|
}
|
|
|
|
addrconf_addr_solict_mult(&wowlan_data->target_ipv6_addrs[i],
|
|
&solicited_addr);
|
|
for (j = 0; j < n_nsc && j < c; j++)
|
|
if (ipv6_addr_cmp(&nsc[j].dest_ipv6_addr,
|
|
&solicited_addr) == 0)
|
|
break;
|
|
if (j == c)
|
|
c++;
|
|
addrs[i].addr = wowlan_data->target_ipv6_addrs[i];
|
|
addrs[i].config_num = cpu_to_le32(j);
|
|
nsc[j].dest_ipv6_addr = solicited_addr;
|
|
memcpy(nsc[j].target_mac_addr, vif->addr, ETH_ALEN);
|
|
}
|
|
|
|
if (wowlan_data->num_target_ipv6_addrs - num_skipped)
|
|
enabled |= IWL_D3_PROTO_IPV6_VALID;
|
|
|
|
cmd->num_valid_ipv6_addrs = cpu_to_le32(i - num_skipped);
|
|
if (enabled & IWL_D3_PROTO_IPV6_VALID)
|
|
enabled |= IWL_D3_PROTO_OFFLOAD_NS;
|
|
#endif
|
|
|
|
if (vif->cfg.arp_addr_cnt) {
|
|
enabled |= IWL_D3_PROTO_OFFLOAD_ARP | IWL_D3_PROTO_IPV4_VALID;
|
|
cmd->common.host_ipv4_addr = vif->cfg.arp_addr_list[0];
|
|
ether_addr_copy(cmd->common.arp_mac_addr, vif->addr);
|
|
}
|
|
|
|
enabled |= IWL_D3_PROTO_OFFLOAD_BTM;
|
|
cmd->common.enabled = cpu_to_le32(enabled);
|
|
cmd->sta_id = cpu_to_le32(ap_sta_id);
|
|
hcmd.data[0] = cmd;
|
|
return iwl_mld_send_cmd(mld, &hcmd);
|
|
}
|
|
|
|
static int
|
|
iwl_mld_wowlan_config(struct iwl_mld *mld, struct ieee80211_vif *bss_vif,
|
|
struct cfg80211_wowlan *wowlan)
|
|
{
|
|
struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(bss_vif);
|
|
struct ieee80211_sta *ap_sta = mld_vif->ap_sta;
|
|
struct iwl_wowlan_config_cmd wowlan_config_cmd = {
|
|
.offloading_tid = IWL_WOWLAN_OFFLOAD_TID,
|
|
};
|
|
u32 sta_id_mask;
|
|
int ap_sta_id, ret;
|
|
int link_id = iwl_mld_get_primary_link(bss_vif);
|
|
struct ieee80211_bss_conf *link_conf;
|
|
|
|
ret = iwl_mld_block_emlsr_sync(mld, bss_vif,
|
|
IWL_MLD_EMLSR_BLOCKED_WOWLAN, link_id);
|
|
if (ret)
|
|
return ret;
|
|
|
|
link_conf = link_conf_dereference_protected(bss_vif, link_id);
|
|
|
|
if (WARN_ON(!ap_sta || !link_conf))
|
|
return -EINVAL;
|
|
|
|
sta_id_mask = iwl_mld_fw_sta_id_mask(mld, ap_sta);
|
|
if (WARN_ON(hweight32(sta_id_mask) != 1))
|
|
return -EINVAL;
|
|
|
|
ap_sta_id = __ffs(sta_id_mask);
|
|
wowlan_config_cmd.sta_id = ap_sta_id;
|
|
|
|
ret = iwl_mld_ensure_queue(mld,
|
|
ap_sta->txq[wowlan_config_cmd.offloading_tid]);
|
|
if (ret)
|
|
return ret;
|
|
|
|
iwl_mld_set_wowlan_config_cmd(mld, wowlan,
|
|
&wowlan_config_cmd, ap_sta);
|
|
ret = iwl_mld_send_cmd_pdu(mld, WOWLAN_CONFIGURATION,
|
|
&wowlan_config_cmd);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = iwl_mld_suspend_send_security_cmds(mld, bss_vif, mld_vif,
|
|
ap_sta_id);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = iwl_mld_send_patterns(mld, wowlan, ap_sta_id);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = iwl_mld_send_proto_offload(mld, bss_vif, ap_sta_id);
|
|
if (ret)
|
|
return ret;
|
|
|
|
iwl_mld_enable_beacon_filter(mld, link_conf, true);
|
|
return iwl_mld_update_mac_power(mld, bss_vif, true);
|
|
}
|
|
|
|
int iwl_mld_wowlan_suspend(struct iwl_mld *mld, struct cfg80211_wowlan *wowlan)
|
|
{
|
|
struct ieee80211_vif *bss_vif;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
if (WARN_ON(!wowlan))
|
|
return 1;
|
|
|
|
IWL_DEBUG_WOWLAN(mld, "Starting the wowlan suspend flow\n");
|
|
|
|
bss_vif = iwl_mld_get_bss_vif(mld);
|
|
if (WARN_ON(!bss_vif))
|
|
return 1;
|
|
|
|
if (!bss_vif->cfg.assoc) {
|
|
int ret;
|
|
/* If we're not associated, this must be netdetect */
|
|
if (WARN_ON(!wowlan->nd_config))
|
|
return 1;
|
|
|
|
ret = iwl_mld_netdetect_config(mld, bss_vif, wowlan);
|
|
if (!ret)
|
|
mld->netdetect = true;
|
|
|
|
return ret;
|
|
}
|
|
|
|
return iwl_mld_wowlan_config(mld, bss_vif, wowlan);
|
|
}
|
|
|
|
/* Returns 0 on success, 1 if an error occurred in firmware during d3,
|
|
* A negative value is expected only in unrecovreable cases.
|
|
*/
|
|
int iwl_mld_wowlan_resume(struct iwl_mld *mld)
|
|
{
|
|
struct ieee80211_vif *bss_vif;
|
|
struct ieee80211_bss_conf *link_conf;
|
|
struct iwl_mld_netdetect_res netdetect_res;
|
|
struct iwl_mld_resume_data resume_data = {
|
|
.notifs_expected =
|
|
IWL_D3_NOTIF_WOWLAN_INFO |
|
|
IWL_D3_NOTIF_D3_END_NOTIF,
|
|
.netdetect_res = &netdetect_res,
|
|
};
|
|
int link_id;
|
|
int ret;
|
|
bool fw_err = false;
|
|
|
|
lockdep_assert_wiphy(mld->wiphy);
|
|
|
|
IWL_DEBUG_WOWLAN(mld, "Starting the wowlan resume flow\n");
|
|
|
|
if (!mld->fw_status.in_d3) {
|
|
IWL_DEBUG_WOWLAN(mld,
|
|
"Device_powered_off() was called during wowlan\n");
|
|
goto err;
|
|
}
|
|
|
|
mld->fw_status.resuming = true;
|
|
mld->fw_status.in_d3 = false;
|
|
mld->scan.last_start_time_jiffies = jiffies;
|
|
|
|
bss_vif = iwl_mld_get_bss_vif(mld);
|
|
if (WARN_ON(!bss_vif))
|
|
goto err;
|
|
|
|
/* We can't have several links upon wowlan entry,
|
|
* this is enforced in the suspend flow.
|
|
*/
|
|
WARN_ON(hweight16(bss_vif->active_links) > 1);
|
|
link_id = bss_vif->active_links ? __ffs(bss_vif->active_links) : 0;
|
|
link_conf = link_conf_dereference_protected(bss_vif, link_id);
|
|
|
|
if (WARN_ON(!link_conf))
|
|
goto err;
|
|
|
|
iwl_fw_dbg_read_d3_debug_data(&mld->fwrt);
|
|
|
|
resume_data.wowlan_status = kzalloc(sizeof(*resume_data.wowlan_status),
|
|
GFP_KERNEL);
|
|
if (!resume_data.wowlan_status)
|
|
return -ENOMEM;
|
|
|
|
if (mld->netdetect)
|
|
resume_data.notifs_expected |= IWL_D3_ND_MATCH_INFO;
|
|
|
|
ret = iwl_mld_wait_d3_notif(mld, &resume_data, true);
|
|
if (ret) {
|
|
IWL_ERR(mld, "Couldn't get the d3 notifs %d\n", ret);
|
|
fw_err = true;
|
|
goto err;
|
|
}
|
|
|
|
if (resume_data.d3_end_flags & IWL_D0I3_RESET_REQUIRE) {
|
|
mld->fw_status.in_hw_restart = true;
|
|
goto process_wakeup_results;
|
|
}
|
|
|
|
iwl_mld_update_changed_regdomain(mld);
|
|
iwl_mld_update_mac_power(mld, bss_vif, false);
|
|
iwl_mld_enable_beacon_filter(mld, link_conf, false);
|
|
iwl_mld_update_device_power(mld, false);
|
|
|
|
if (mld->netdetect)
|
|
ret = iwl_mld_scan_stop(mld, IWL_MLD_SCAN_NETDETECT, false);
|
|
|
|
process_wakeup_results:
|
|
if (mld->netdetect) {
|
|
iwl_mld_process_netdetect_res(mld, bss_vif, &resume_data);
|
|
mld->netdetect = false;
|
|
} else {
|
|
bool keep_connection =
|
|
iwl_mld_process_wowlan_status(mld, bss_vif,
|
|
resume_data.wowlan_status);
|
|
|
|
/* EMLSR state will be cleared if the connection is not kept */
|
|
if (keep_connection)
|
|
iwl_mld_unblock_emlsr(mld, bss_vif,
|
|
IWL_MLD_EMLSR_BLOCKED_WOWLAN);
|
|
else
|
|
ieee80211_resume_disconnect(bss_vif);
|
|
}
|
|
|
|
goto out;
|
|
|
|
err:
|
|
if (fw_err) {
|
|
mld->trans->state = IWL_TRANS_NO_FW;
|
|
set_bit(STATUS_FW_ERROR, &mld->trans->status);
|
|
}
|
|
|
|
mld->fw_status.in_hw_restart = true;
|
|
ret = 1;
|
|
out:
|
|
mld->fw_status.resuming = false;
|
|
|
|
if (resume_data.wowlan_status) {
|
|
kfree(resume_data.wowlan_status->wake_packet);
|
|
kfree(resume_data.wowlan_status);
|
|
}
|
|
|
|
return ret;
|
|
}
|