switch (key->cipher) { case WLAN_CIPHER_SUITE_WEP40: case WLAN_CIPHER_SUITE_WEP104: { /* hack it for now */
DEFINE_RAW_FLEX(struct iwl_mvm_wep_key_cmd, wkc, wep_key, 1); struct iwl_mvm_wep_key *wep_key = wkc->wep_key;
/* * This will fail -- the key functions don't set support * pairwise WEP keys. However, that's better than silently * failing WoWLAN. Or maybe not?
*/ if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) break;
memcpy(&wep_key->key[3], key->key, key->keylen); if (key->keyidx == mvmvif->tx_key_idx) { /* TX key must be at offset 0 */
wep_key->key_offset = 0;
} else { /* others start at 1 */
data->wep_key_idx++;
wep_key->key_offset = data->wep_key_idx;
}
mutex_lock(&mvm->mutex);
ret = iwl_mvm_send_cmd_pdu(mvm, WEP_KEY, 0,
__struct_size(wkc), wkc);
data->error = ret != 0;
/* don't upload key again */ return;
} default:
data->error = true; return; case WLAN_CIPHER_SUITE_BIP_GMAC_256: case WLAN_CIPHER_SUITE_BIP_GMAC_128: return; case WLAN_CIPHER_SUITE_AES_CMAC: /* * Ignore CMAC keys -- the WoWLAN firmware doesn't support them * but we also shouldn't abort suspend due to that. It does have * support for the IGTK key renewal, but doesn't really use the * IGTK for anything. This means we could spuriously wake up or * be deauthenticated, but that was considered acceptable.
*/ return; case WLAN_CIPHER_SUITE_TKIP: case WLAN_CIPHER_SUITE_CCMP: case WLAN_CIPHER_SUITE_GCMP: case WLAN_CIPHER_SUITE_GCMP_256: break;
}
mutex_lock(&mvm->mutex); /* * The D3 firmware hardcodes the key offset 0 as the key it * uses to transmit packets to the AP, i.e. the PTK.
*/ if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) {
mvm->ptk_ivlen = key->iv_len;
mvm->ptk_icvlen = key->icv_len;
ret = iwl_mvm_set_sta_key(mvm, vif, sta, key, 0);
} else { /* * firmware only supports TSC/RSC for a single key, * so if there are multiple keep overwriting them * with new ones -- this relies on mac80211 doing * list_add_tail().
*/
mvm->gtk_ivlen = key->iv_len;
mvm->gtk_icvlen = key->icv_len;
ret = iwl_mvm_set_sta_key(mvm, vif, sta, key, 1);
}
mutex_unlock(&mvm->mutex);
data->error = ret != 0;
}
/* * For non-QoS this relies on the fact that both the uCode and * mac80211 use TID 0 (as they need to avoid replay attacks) * for checking the IV in the frames.
*/ for (i = 0; i < IWL_NUM_RSC; i++) {
ieee80211_get_key_rx_seq(key, i, &seq);
tkip_sc[i].iv16 = cpu_to_le16(seq.tkip.iv16);
tkip_sc[i].iv32 = cpu_to_le32(seq.tkip.iv32);
}
data->have_rsc_tsc = true; break; case WLAN_CIPHER_SUITE_CCMP: case WLAN_CIPHER_SUITE_GCMP: case WLAN_CIPHER_SUITE_GCMP_256: if (sta) { struct aes_sc *aes_tx_sc;
u64 pn64;
/* * For non-QoS this relies on the fact that both the uCode and * mac80211/our RX code use TID 0 for checking the PN.
*/ if (sta && iwl_mvm_has_new_rx_api(mvm)) { struct iwl_mvm_sta *mvmsta; struct iwl_mvm_key_pn *ptk_pn; const u8 *pn;
/* only for ciphers that can be PTK/GTK */ switch (key->cipher) { default: return; case WLAN_CIPHER_SUITE_TKIP: case WLAN_CIPHER_SUITE_CCMP: case WLAN_CIPHER_SUITE_GCMP: case WLAN_CIPHER_SUITE_GCMP_256: break;
}
if (sta) {
rsc = data->rsc->ucast_rsc;
} else { if (WARN_ON(data->gtks >= ARRAY_SIZE(data->gtk_ids))) return;
data->gtk_ids[data->gtks] = key->keyidx;
rsc = data->rsc->mcast_rsc[data->gtks % 2]; if (WARN_ON(key->keyidx >=
ARRAY_SIZE(data->rsc->mcast_key_id_map))) return;
data->rsc->mcast_key_id_map[key->keyidx] = data->gtks % 2; if (data->gtks >= 2) { int prev = data->gtks - 2; int prev_idx = data->gtk_ids[prev];
switch (key->cipher) { default:
WARN_ON(1); break; case WLAN_CIPHER_SUITE_TKIP:
/* * For non-QoS this relies on the fact that both the uCode and * mac80211 use TID 0 (as they need to avoid replay attacks) * for checking the IV in the frames.
*/ for (i = 0; i < IWL_MAX_TID_COUNT; i++) {
ieee80211_get_key_rx_seq(key, i, &seq);
data->have_rsc = true; break; case WLAN_CIPHER_SUITE_CCMP: case WLAN_CIPHER_SUITE_GCMP: case WLAN_CIPHER_SUITE_GCMP_256: /* * For non-QoS this relies on the fact that both the uCode and * mac80211/our RX code use TID 0 for checking the PN.
*/ if (sta) { struct iwl_mvm_sta *mvmsta; struct iwl_mvm_key_pn *ptk_pn; const u8 *pn;
staticint iwl_mvm_wowlan_config_rsc_tsc(struct iwl_mvm *mvm, struct ieee80211_vif *vif, struct iwl_mvm_vif_link_info *mvm_link)
{ int ver = iwl_fw_lookup_cmd_ver(mvm->fw, WOWLAN_TSC_RSC_PARAM,
IWL_FW_CMD_VER_UNKNOWN); int ret;
if (ver == 5) { struct wowlan_key_rsc_v5_data data = {}; int i;
data.rsc = kzalloc(sizeof(*data.rsc), GFP_KERNEL); if (!data.rsc) return -ENOMEM;
for (i = 0; i < ARRAY_SIZE(data.rsc->mcast_key_id_map); i++)
data.rsc->mcast_key_id_map[i] =
IWL_MCAST_KEY_MAP_INVALID;
data.rsc->sta_id = cpu_to_le32(mvm_link->ap_sta_id);
for (i = 0; i < IWL_NUM_RSC; i++) {
ieee80211_get_key_rx_seq(key, i, &seq); /* wrapping isn't allowed, AP must rekey */ if (seq.tkip.iv32 > cur_rx_iv32)
cur_rx_iv32 = seq.tkip.iv32;
}
switch (key->cipher) { default: return; case WLAN_CIPHER_SUITE_TKIP: if (!sta)
data->kek_kck_cmd->gtk_cipher =
cpu_to_le32(STA_KEY_FLG_TKIP); return; case WLAN_CIPHER_SUITE_BIP_GMAC_256: case WLAN_CIPHER_SUITE_BIP_GMAC_128: if (cipher)
*cipher = cpu_to_le32(STA_KEY_FLG_GCMP); return; case WLAN_CIPHER_SUITE_AES_CMAC: case WLAN_CIPHER_SUITE_BIP_CMAC_256: if (cipher)
*cipher = cpu_to_le32(STA_KEY_FLG_CCM); return; case WLAN_CIPHER_SUITE_CCMP: if (!sta)
data->kek_kck_cmd->gtk_cipher =
cpu_to_le32(STA_KEY_FLG_CCM); return; case WLAN_CIPHER_SUITE_GCMP: case WLAN_CIPHER_SUITE_GCMP_256: if (!sta)
data->kek_kck_cmd->gtk_cipher =
cpu_to_le32(STA_KEY_FLG_GCMP); return;
}
}
iwl_mvm_stop_device(mvm); /* * Set the HW restart bit -- this is mostly true as we're * going to load new firmware and reprogram that, though * the reprogramming is going to be manual to avoid adding * all the MACs that aren't support. * We don't have to clear up everything though because the * reprogramming is manual. When we resume, we'll actually * go through a proper restart sequence again to switch * back to the runtime firmware image.
*/
set_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status);
/* the fw is reset, so all the keys are cleared */
memset(mvm->fw_key_table, 0, sizeof(mvm->fw_key_table));
if (ap_sta->mfp)
wowlan_config_cmd->flags |= IS_11W_ASSOC;
if (iwl_fw_lookup_cmd_ver(mvm->fw, WOWLAN_CONFIGURATION, 0) < 6) { /* Query the last used seqno and set it */ int ret = iwl_mvm_get_last_nonqos_seq(mvm, vif);
if (iwl_fw_lookup_cmd_ver(mvm->fw, WOWLAN_CONFIGURATION, 0) < 7)
iwl_mvm_set_wowlan_qos_seq(mvm_ap_sta, wowlan_config_cmd);
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->tcp) { /* * Set the "link change" (really "link lost") flag as well * since that implies losing the TCP connection.
*/
wowlan_config_cmd->wakeup_filter |=
cpu_to_le32(IWL_WOWLAN_WAKEUP_REMOTE_LINK_LOSS |
IWL_WOWLAN_WAKEUP_REMOTE_SIGNATURE_TABLE |
IWL_WOWLAN_WAKEUP_REMOTE_WAKEUP_PACKET |
IWL_WOWLAN_WAKEUP_LINK_CHANGE);
}
if (!unified) { /* * if we have to configure keys, call ieee80211_iter_keys(), * as we need non-atomic context in order to take the * required locks.
*/ /* * Note that currently we don't use CMD_ASYNC in the iterator. * In case of key_data.configure_keys, all the configured * commands are SYNC, and iwl_mvm_wowlan_program_keys() will * take care of locking/unlocking mvm->mutex.
*/
ieee80211_iter_keys(mvm->hw, vif, iwl_mvm_wowlan_program_keys,
&key_data);
if (key_data.error) return -EIO;
}
ret = iwl_mvm_wowlan_config_rsc_tsc(mvm, vif, mvm_link); if (ret) return ret;
if (!fw_has_api(&mvm->fw->ucode_capa,
IWL_UCODE_TLV_API_TKIP_MIC_KEYS)) { int ver = iwl_fw_lookup_cmd_ver(mvm->fw, WOWLAN_TKIP_PARAM,
IWL_FW_CMD_VER_UNKNOWN); struct wowlan_key_tkip_data tkip_data = {}; int size;
if (tkip_data.have_tkip_keys) { /* send relevant data according to CMD version */
ret = iwl_mvm_send_cmd_pdu(mvm,
WOWLAN_TKIP_PARAM,
CMD_ASYNC, size,
&tkip_data.tkip); if (ret) return ret;
}
}
/* configure rekey data only if offloaded rekey is supported (d3) */ if (mvmvif->rekey_data.valid) { struct iwl_wowlan_kek_kck_material_cmd_v4 kek_kck_cmd = {}; struct iwl_wowlan_kek_kck_material_cmd_v4 *_kek_kck_cmd =
&kek_kck_cmd; struct wowlan_key_gtk_type_iter gtk_type_data = {
.kek_kck_cmd = _kek_kck_cmd,
};
ret = iwl_mvm_send_cmd_pdu(mvm, WOWLAN_CONFIGURATION, 0, sizeof(wowlan_config_cmd),
&wowlan_config_cmd);
} else {
ret = iwl_mvm_send_cmd_pdu(mvm, WOWLAN_CONFIGURATION, 0, sizeof(*wowlan_config_cmd_v6),
wowlan_config_cmd_v6);
} if (ret) return ret;
if (fw_has_api(&mvm->fw->ucode_capa,
IWL_UCODE_TLV_API_WOWLAN_TCP_SYN_WAKE))
ret = iwl_mvm_send_patterns(mvm, mvm_link, wowlan); else
ret = iwl_mvm_send_patterns_v1(mvm, wowlan); if (ret) return ret;
if (!unified_image) {
ret = iwl_mvm_switch_to_d3(mvm); if (ret) return ret;
} else { /* In theory, we wouldn't have to stop a running sched * scan in order to start another one (for * net-detect). But in practice this doesn't seem to * work properly, so stop any running sched_scan now.
*/
ret = iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_SCHED, true); if (ret) return ret;
}
ret = iwl_mvm_sched_scan_start(mvm, vif, nd_config, &mvm->nd_ies,
IWL_MVM_SCAN_NETDETECT); if (ret) return ret;
if (WARN_ON(mvm->nd_match_sets || mvm->nd_channels)) return -EBUSY;
/* save the sched scan matchsets... */ if (nd_config->n_match_sets) {
mvm->nd_match_sets = kmemdup(nd_config->match_sets, sizeof(*nd_config->match_sets) *
nd_config->n_match_sets,
GFP_KERNEL); if (mvm->nd_match_sets)
mvm->n_nd_match_sets = nd_config->n_match_sets;
}
/* ...and the sched scan channels for later reporting */
mvm->nd_channels = kmemdup(nd_config->channels, sizeof(*nd_config->channels) *
nd_config->n_channels,
GFP_KERNEL); if (mvm->nd_channels)
mvm->n_nd_channels = nd_config->n_channels;
staticint __iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan, bool test)
{ struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); struct ieee80211_vif *vif = NULL; struct iwl_mvm_vif *mvmvif = NULL; struct ieee80211_sta *ap_sta = NULL; struct iwl_mvm_vif_link_info *mvm_link; struct iwl_d3_manager_config d3_cfg_cmd_data = { /* * Program the minimum sleep time to 10 seconds, as many * platforms have issues processing a wakeup signal while * still being in the process of suspending.
*/
.min_sleep_time = cpu_to_le32(10 * 1000 * 1000),
}; struct iwl_host_cmd d3_cfg_cmd = {
.id = D3_CONFIG_CMD,
.flags = CMD_WANT_SKB,
.data[0] = &d3_cfg_cmd_data,
.len[0] = sizeof(d3_cfg_cmd_data),
}; int ret; int len __maybe_unused; bool unified_image = fw_has_capa(&mvm->fw->ucode_capa,
IWL_UCODE_TLV_CAPA_CNSLDTD_D3_D0_IMG);
if (!wowlan) { /* * mac80211 shouldn't get here, but for D3 test * it doesn't warrant a warning
*/
WARN_ON(!test); return -EINVAL;
}
vif = iwl_mvm_get_bss_vif(mvm); if (IS_ERR_OR_NULL(vif)) return 1;
ret = iwl_mvm_block_esr_sync(mvm, vif, IWL_MVM_ESR_BLOCKED_WOWLAN); if (ret) return ret;
mutex_lock(&mvm->mutex);
set_bit(IWL_MVM_STATUS_IN_D3, &mvm->status);
synchronize_net();
mvmvif = iwl_mvm_vif_from_mac80211(vif);
mvm_link = mvmvif->link[iwl_mvm_get_primary_link(vif)]; if (WARN_ON_ONCE(!mvm_link)) {
ret = -EINVAL; goto out_noreset;
}
if (mvm_link->ap_sta_id == IWL_INVALID_STA) { /* if we're not associated, this must be netdetect */ if (!wowlan->nd_config) {
ret = 1; goto out_noreset;
}
ret = iwl_mvm_netdetect_config(
mvm, wowlan, wowlan->nd_config, vif); if (ret) goto out;
ap_sta = rcu_dereference_protected(
mvm->fw_id_to_mac_id[mvm_link->ap_sta_id],
lockdep_is_held(&mvm->mutex)); if (IS_ERR_OR_NULL(ap_sta)) {
ret = -EINVAL; goto out_noreset;
}
ret = iwl_mvm_sta_ensure_queue(
mvm, ap_sta->txq[wowlan_config_cmd.offloading_tid]); if (ret) goto out_noreset;
ret = iwl_mvm_get_wowlan_config(mvm, wowlan, &wowlan_config_cmd,
vif, mvmvif, ap_sta); if (ret) goto out_noreset;
ret = iwl_mvm_wowlan_config(mvm, wowlan, &wowlan_config_cmd,
vif, mvmvif, mvm_link, ap_sta); if (ret) goto out;
mvm->net_detect = false;
}
ret = iwl_mvm_power_update_device(mvm); if (ret) goto out;
ret = iwl_mvm_power_update_mac(mvm); if (ret) goto out;
#ifdef CONFIG_IWLWIFI_DEBUGFS if (mvm->d3_wake_sysassert)
d3_cfg_cmd_data.wakeup_flags |=
cpu_to_le32(IWL_WAKEUP_D3_CONFIG_FW_ERROR); #endif
/* * Prior to 9000 device family the driver needs to stop the dbg * recording before entering D3. In later devices the FW stops the * recording automatically.
*/ if (mvm->trans->mac_cfg->device_family < IWL_DEVICE_FAMILY_9000)
iwl_fw_dbg_stop_restart_recording(&mvm->fwrt, NULL, true);
/* must be last -- this switches firmware state */
ret = iwl_mvm_send_cmd(mvm, &d3_cfg_cmd); if (ret) goto out; #ifdef CONFIG_IWLWIFI_DEBUGFS
len = iwl_rx_packet_payload_len(d3_cfg_cmd.resp_pkt); if (len >= sizeof(u32)) {
mvm->d3_test_pme_ptr =
le32_to_cpup((__le32 *)d3_cfg_cmd.resp_pkt->data);
} #endif
iwl_free_resp(&d3_cfg_cmd);
/* converted data from the different status responses */ struct iwl_wowlan_status_data {
u64 replay_ctr;
u32 num_of_gtk_rekeys;
u32 received_beacons;
u32 wakeup_reasons;
u32 wake_packet_length;
u32 wake_packet_bufsize;
u16 pattern_number;
u16 non_qos_seq_ctr;
u16 qos_seq_ctr[8];
u8 tid_tear_down;
u8 tid_offloaded_tx;
struct { /* including RX MIC key for TKIP */
u8 key[WOWLAN_KEY_MAX_SIZE];
u8 len;
u8 flags;
u8 id;
} gtk[WOWLAN_GTK_KEYS_NUM];
struct { /* * We store both the TKIP and AES representations * coming from the firmware because we decode the * data from there before we iterate the keys and * know which one we need.
*/ struct { struct ieee80211_key_seq seq[IWL_MAX_TID_COUNT];
} tkip, aes;
/* * We use -1 for when we have valid data but don't know * the key ID from firmware, and thus it needs to be * installed with the last key (depending on rekeying).
*/
s8 key_id; bool valid;
} gtk_seq[2];
struct { /* Same as above */ struct { struct ieee80211_key_seq seq[IWL_MAX_TID_COUNT];
u64 tx_pn;
} tkip, aes;
} ptk;
if (ieee80211_has_protected(hdr->frame_control)) { /* * This is unlocked and using gtk_i(c)vlen, * but since everything is under RTNL still * that's not really a problem - changing * it would be difficult.
*/ if (is_multicast_ether_addr(hdr->addr1)) {
ivlen = mvm->gtk_ivlen;
icvlen += mvm->gtk_icvlen;
} else {
ivlen = mvm->ptk_ivlen;
icvlen += mvm->ptk_icvlen;
}
}
/* if truncated, FCS/ICV is (partially) gone */ if (truncated >= icvlen) {
icvlen = 0;
truncated -= icvlen;
} else {
icvlen -= truncated;
truncated = 0;
}
rcu_read_lock();
ptk_pn = rcu_dereference(mvmsta->ptk_pn[key->keyidx]); if (WARN_ON(!ptk_pn)) {
rcu_read_unlock(); return;
}
for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { int i;
for (i = 1; i < mvm->trans->info.num_rxqs; i++)
memcpy(ptk_pn->q[i].pn[tid],
status->ptk.aes.seq[tid].ccmp.pn,
IEEE80211_CCMP_PN_LEN);
}
rcu_read_unlock();
}
staticvoid iwl_mvm_convert_key_counters(struct iwl_wowlan_status_data *status, union iwl_all_tsc_rsc *sc, u8 key_idx)
{ int i;
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: /* we support these */
data->gtk_cipher = key->cipher; 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: /* we support these */ if (data->igtk_support &&
(key->keyidx == 4 || key->keyidx == 5)) {
data->igtk_cipher = key->cipher;
} elseif (data->bigtk_support &&
(key->keyidx == 6 || key->keyidx == 7)) {
data->bigtk_cipher = key->cipher;
} else {
data->unhandled_cipher = true; return;
} break; default: /* everything else - disconnect from AP */
data->unhandled_cipher = true; return;
}
data->num_keys++;
}
staticvoid
iwl_mvm_d3_set_igtk_bigtk_ipn(conststruct iwl_multicast_key_data *key, struct ieee80211_key_seq *seq, u32 cipher)
{ switch (cipher) { case WLAN_CIPHER_SUITE_BIP_GMAC_128: case WLAN_CIPHER_SUITE_BIP_GMAC_256:
BUILD_BUG_ON(sizeof(seq->aes_gmac.pn) != sizeof(key->ipn));
memcpy(seq->aes_gmac.pn, key->ipn, sizeof(seq->aes_gmac.pn)); break; case WLAN_CIPHER_SUITE_BIP_CMAC_256: case WLAN_CIPHER_SUITE_AES_CMAC:
BUILD_BUG_ON(sizeof(seq->aes_cmac.pn) != sizeof(key->ipn));
memcpy(seq->aes_cmac.pn, key->ipn, sizeof(seq->aes_cmac.pn)); break; default:
WARN_ON(1);
}
}
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: if (sta) {
atomic64_set(&key->tx_pn, status->ptk.aes.tx_pn);
iwl_mvm_set_aes_ptk_rx_seq(data->mvm, status, sta, key); return;
}
fallthrough; case WLAN_CIPHER_SUITE_TKIP: if (sta) {
atomic64_set(&key->tx_pn, status->ptk.tkip.tx_pn);
iwl_mvm_set_key_rx_seq_tids(key, status->ptk.tkip.seq); return;
}
keyidx = key->keyidx; /* * Update the seq even if there was a rekey. If there was a * rekey, we will update again after replacing the key
*/ if ((status->gtk[0].len && keyidx == status->gtk[0].id) ||
(status->gtk[1].len && keyidx == status->gtk[1].id))
iwl_mvm_set_key_rx_seq(key, status); 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) {
iwl_mvm_d3_update_igtk_bigtk(status, key,
&status->igtk);
} if (key->keyidx == 6 || key->keyidx == 7) {
u8 idx = key->keyidx == status->bigtk[1].id;
key = ieee80211_gtk_rekey_add(vif, status->gtk[i].id, key_data, sizeof(key_data), link_id); if (IS_ERR(key)) { /* FW may send also the old keys */ if (PTR_ERR(key) == -EALREADY) continue; returnfalse;
}
if (iwl_fw_lookup_notif_ver(mvm->fw, PROT_OFFLOAD_GROUP,
WOWLAN_INFO_NOTIFICATION,
0) >= 3)
gtkdata.bigtk_support = true;
/* find last GTK that we used initially, if any */
ieee80211_iter_keys(mvm->hw, vif,
iwl_mvm_d3_find_last_keys, >kdata); /* not trying to keep connections with MFP/unhandled ciphers */ if (gtkdata.unhandled_cipher) returnfalse; if (!gtkdata.num_keys) goto out;
/* * invalidate all other GTKs that might still exist and update * the one that we used
*/
ieee80211_iter_keys(mvm->hw, vif,
iwl_mvm_d3_update_keys, >kdata);
if (status->num_of_gtk_rekeys) {
__be64 replay_ctr = cpu_to_be64(status->replay_ctr);
IWL_DEBUG_WOWLAN(mvm, "num of GTK rekeying %d\n",
status->num_of_gtk_rekeys);
if (!iwl_mvm_gtk_rekey(status, vif, mvm, gtkdata.gtk_cipher)) returnfalse;
if (!iwl_mvm_d3_igtk_bigtk_rekey_add(status, vif,
gtkdata.igtk_cipher,
&status->igtk)) returnfalse;
for (i = 0; i < ARRAY_SIZE(status->bigtk); i++) { if (!iwl_mvm_d3_igtk_bigtk_rekey_add(status, vif,
gtkdata.bigtk_cipher,
&status->bigtk[i])) returnfalse;
}
out: if (iwl_fw_lookup_notif_ver(mvm->fw, LONG_GROUP,
WOWLAN_GET_STATUSES,
IWL_FW_CMD_VER_UNKNOWN) < 10) {
mvmvif->seqno_valid = true; /* +0x10 because the set API expects next-to-use, not last-used */
mvmvif->seqno = status->non_qos_seq_ctr + 0x10;
}
if (status->wakeup_reasons & disconnection_reasons) returnfalse;
/* if it's as long as the TKIP encryption key, copy MIC key */ if (status->gtk[0].len == NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY)
memcpy(status->gtk[0].key + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY,
data->tkip_mic_key, sizeof(data->tkip_mic_key));
}
/* if it's as long as the TKIP encryption key, copy MIC key */ if (status->gtk[status_idx].len ==
NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY)
memcpy(status->gtk[status_idx].key +
NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY,
data[data_idx].tkip_mic_key, sizeof(data[data_idx].tkip_mic_key));
status_idx++;
}
}
staticvoid iwl_mvm_convert_igtk(struct iwl_wowlan_status_data *status, struct iwl_wowlan_igtk_status *data)
{ int i;
/* mac80211 expects big endian for memcmp() to work, convert */ for (i = 0; i < sizeof(data->ipn); i++)
status->igtk.ipn[i] = data->ipn[sizeof(data->ipn) - i - 1];
}
/* copy GTK info to the right place */
memcpy(status->gtk[0].key, v6->gtk.decrypt_key, sizeof(v6->gtk.decrypt_key));
memcpy(status->gtk[0].key + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY,
v6->gtk.tkip_mic_key, sizeof(v6->gtk.tkip_mic_key));
/* hardcode the key length to 16 since v6 only supports 16 */
status->gtk[0].len = 16;
/* * The key index only uses 2 bits (values 0 to 3) and * we always set bit 7 which means this is the * currently used key.
*/
status->gtk[0].flags = v6->gtk.key_index | BIT(7);
status->gtk[0].id = v6->gtk.key_index;
} elseif (notif_ver == 7) { struct iwl_wowlan_status_v7 *v7 = (void *)cmd.resp_pkt->data;
status = iwl_mvm_parse_wowlan_status_common_v7(mvm, v7, len); if (!status) goto out_free_resp;
mvm_ap_sta = iwl_mvm_sta_from_staid_protected(mvm, mvm_link->ap_sta_id); if (!mvm_ap_sta) goto out_unlock;
/* firmware stores last-used value, we store next value */ if (wowlan_info_ver >= 5) {
mvm_ap_sta->tid_data[status->tid_offloaded_tx].seq_number =
status->qos_seq_ctr[status->tid_offloaded_tx] + 0x10;
} else { for (i = 0; i < IWL_MAX_TID_COUNT; i++)
mvm_ap_sta->tid_data[i].seq_number =
status->qos_seq_ctr[i] + 0x10;
}
if (mvm->trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_22000) {
i = mvm->offload_tid;
iwl_trans_set_q_ptrs(mvm->trans,
mvm_ap_sta->tid_data[i].txq_id,
mvm_ap_sta->tid_data[i].seq_number >> 4);
}
staticint iwl_mvm_query_num_match_chans(struct iwl_mvm *mvm, struct iwl_mvm_nd_results *results, int idx)
{ int n_chans = 0, i;
if (fw_has_api(&mvm->fw->ucode_capa,
IWL_UCODE_TLV_API_SCAN_OFFLOAD_CHANS)) { struct iwl_scan_offload_profile_match *matches =
(void *)results->matches;
for (i = 0; i < SCAN_OFFLOAD_MATCHING_CHANNELS_LEN; i++)
n_chans += hweight8(matches[idx].matching_channels[i]);
} else { struct iwl_scan_offload_profile_match_v1 *matches =
(void *)results->matches;
for (i = 0; i < SCAN_OFFLOAD_MATCHING_CHANNELS_LEN_V1; i++)
n_chans += hweight8(matches[idx].matching_channels[i]);
}
return n_chans;
}
staticvoid iwl_mvm_query_set_freqs(struct iwl_mvm *mvm, struct iwl_mvm_nd_results *results, struct cfg80211_wowlan_nd_match *match, int idx)
{ int i; int n_channels = 0;
if (fw_has_api(&mvm->fw->ucode_capa,
IWL_UCODE_TLV_API_SCAN_OFFLOAD_CHANS)) { struct iwl_scan_offload_profile_match *matches =
(void *)results->matches;
for (i = 0; i < SCAN_OFFLOAD_MATCHING_CHANNELS_LEN * 8; i++) if (matches[idx].matching_channels[i / 8] & (BIT(i % 8)))
match->channels[n_channels++] =
mvm->nd_channels[i]->center_freq;
} else { struct iwl_scan_offload_profile_match_v1 *matches =
(void *)results->matches;
for (i = 0; i < SCAN_OFFLOAD_MATCHING_CHANNELS_LEN_V1 * 8; i++) if (matches[idx].matching_channels[i / 8] & (BIT(i % 8)))
match->channels[n_channels++] =
mvm->nd_channels[i]->center_freq;
} /* We may have ended up with fewer channels than we allocated. */
match->n_channels = n_channels;
}
/** * enum iwl_d3_notif - d3 notifications * @IWL_D3_NOTIF_WOWLAN_INFO: WOWLAN_INFO_NOTIF was received * @IWL_D3_NOTIF_WOWLAN_WAKE_PKT: WOWLAN_WAKE_PKT_NOTIF was received * @IWL_D3_NOTIF_PROT_OFFLOAD: PROT_OFFLOAD_NOTIF was received * @IWL_D3_ND_MATCH_INFO: OFFLOAD_MATCH_INFO_NOTIF was received * @IWL_D3_NOTIF_D3_END_NOTIF: D3_END_NOTIF was received
*/ enum iwl_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)
};
matched_profiles = d3_data->nd_results->matched_profiles; if (mvm->n_nd_match_sets) {
n_matches = hweight_long(matched_profiles);
} else {
IWL_ERR(mvm, "no net detect match information available\n");
n_matches = 0;
}
match = kzalloc(struct_size(match, channels, n_channels),
GFP_KERNEL); if (!match) goto out_report_nd;
match->n_channels = n_channels;
net_detect->matches[n_matches++] = match;
/* We inverted the order of the SSIDs in the scan * request, so invert the index here.
*/
idx = mvm->n_nd_match_sets - i - 1;
match->ssid.ssid_len = mvm->nd_match_sets[idx].ssid.ssid_len;
memcpy(match->ssid.ssid, mvm->nd_match_sets[idx].ssid.ssid,
match->ssid.ssid_len);
if (mvm->n_nd_channels < n_channels) continue;
iwl_mvm_query_set_freqs(mvm, d3_data->nd_results, match, i);
} /* We may have fewer matches than we allocated. */
net_detect->n_matches = n_matches;
if (net_detect) { for (i = 0; i < net_detect->n_matches; i++)
kfree(net_detect->matches[i]);
kfree(net_detect);
}
}
staticvoid iwl_mvm_d3_disconnect_iter(void *data, u8 *mac, struct ieee80211_vif *vif)
{ /* skip the one we keep connection on */ if (data == vif) return;
if (vif->type == NL80211_IFTYPE_STATION)
ieee80211_resume_disconnect(vif);
}
/* check if we have lmac2 set and check for error */ if (iwl_fwrt_read_err_table(mvm->trans,
mvm->trans->dbg.lmac_error_event_table[1],
NULL)) return FW_ERROR;
/* check for umac error */ if (iwl_fwrt_read_err_table(mvm->trans,
mvm->trans->dbg.umac_error_event_table,
NULL)) return FW_ERROR;
return FW_ALIVE;
}
/* * This function assumes: * 1. The mutex is already held. * 2. The callee functions unlock the mutex.
*/ staticbool
iwl_mvm_choose_query_wakeup_reasons(struct iwl_mvm *mvm, struct ieee80211_vif *vif, struct iwl_d3_data *d3_data)
{
lockdep_assert_held(&mvm->mutex);
/* if FW uses status notification, status shouldn't be NULL here */ if (!d3_data->status) { struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
u8 sta_id = mvm->net_detect ? IWL_INVALID_STA :
mvmvif->deflink.ap_sta_id;
/* bug - FW with MLO has status notification */
WARN_ON(ieee80211_vif_is_mld(vif));
if (len < sizeof(struct iwl_scan_offload_match_info)) {
IWL_ERR(mvm, "Invalid scan match info notification\n"); return;
}
if (!mvm->net_detect) {
IWL_ERR(mvm, "Unexpected scan match info notification\n"); return;
}
if (!status || status->wakeup_reasons != IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS) {
IWL_ERR(mvm, "Ignore scan match info notification: no reason\n"); return;
}
switch (WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd)) { case WIDE_ID(PROT_OFFLOAD_GROUP, WOWLAN_INFO_NOTIFICATION): {
if (d3_data->notif_received & IWL_D3_NOTIF_WOWLAN_INFO) { /* We might get two notifications due to dual bss */
IWL_DEBUG_WOWLAN(mvm, "Got additional wowlan info notification\n"); break;
}
if (d3_data->status &&
d3_data->status->wakeup_reasons & IWL_WOWLAN_WAKEUP_REASON_HAS_WAKEUP_PKT) /* We are supposed to get also wake packet notif */
d3_data->notif_expected |= IWL_D3_NOTIF_WOWLAN_WAKE_PKT;
if (d3_data->notif_received & IWL_D3_NOTIF_WOWLAN_WAKE_PKT) { /* We shouldn't get two wake packet notifications */
IWL_ERR(mvm, "Got additional wowlan wake packet notification\n");
} else {
d3_data->notif_received |= IWL_D3_NOTIF_WOWLAN_WAKE_PKT;
len = iwl_rx_packet_payload_len(pkt);
ret = iwl_mvm_wowlan_store_wake_pkt(mvm, notif,
d3_data->status,
len); if (ret)
IWL_ERR(mvm, "Can't parse WOWLAN_WAKE_PKT_NOTIFICATION\n");
}
ret = iwl_trans_d3_resume(mvm->trans, &d3_status, test, !reset); if (ret) return ret;
if (d3_status != IWL_D3_STATUS_ALIVE) {
IWL_INFO(mvm, "Device was reset during suspend\n"); return -ENOENT;
}
/* * We should trigger resume flow using command only for 22000 family * AX210 and above don't need the command since they have * the doorbell interrupt.
*/ if (mvm->trans->mac_cfg->device_family <= IWL_DEVICE_FAMILY_22000 &&
fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_D0I3_END_FIRST)) {
ret = iwl_mvm_send_cmd(mvm, &cmd); if (ret < 0)
IWL_ERR(mvm, "Failed to send D0I3_END_CMD first (%d)\n",
ret);
}
/* Apparently, the device went away and device_powered_off() was called, * don't even try to read the rt_status, the device is currently * inaccessible.
*/ if (!test_bit(IWL_MVM_STATUS_IN_D3, &mvm->status)) {
IWL_INFO(mvm, "Can't resume, device_powered_off() was called during wowlan\n"); goto err;
}
mvm->last_reset_or_resume_time_jiffies = jiffies;
/* get the BSS vif pointer again */
vif = iwl_mvm_get_bss_vif(mvm); if (IS_ERR_OR_NULL(vif)) goto err;
iwl_fw_dbg_read_d3_debug_data(&mvm->fwrt);
rt_status = iwl_mvm_check_rt_status(mvm, vif); if (rt_status != FW_ALIVE) {
set_bit(STATUS_FW_ERROR, &mvm->trans->status); if (rt_status == FW_ERROR) {
IWL_ERR(mvm, "FW Error occurred during suspend. Restarting.\n");
iwl_mvm_dump_nic_error_log(mvm);
iwl_dbg_tlv_time_point(&mvm->fwrt,
IWL_FW_INI_TIME_POINT_FW_ASSERT,
NULL);
iwl_fw_dbg_collect_desc(&mvm->fwrt,
&iwl_dump_desc_assert, false, 0);
}
ret = 1; goto err;
}
if (resume_notif_based) {
d3_data.status = kzalloc(sizeof(*d3_data.status), GFP_KERNEL); if (!d3_data.status) {
IWL_ERR(mvm, "Failed to allocate wowlan status\n");
ret = -ENOMEM; goto err;
}
ret = iwl_mvm_d3_notif_wait(mvm, &d3_data); if (ret) goto err;
} else {
ret = iwl_mvm_resume_firmware(mvm, test); if (ret < 0) goto err;
}
/* when reset is required we can't send these following commands */ if (d3_data.d3_end_flags & IWL_D0I3_RESET_REQUIRE) goto query_wakeup_reasons;
/* * Query the current location and source from the D3 firmware so we * can play it back when we re-intiailize the D0 firmware
*/
iwl_mvm_update_changed_regdom(mvm);
if (!unified_image) /* Re-configure default SAR profile */
iwl_mvm_sar_select_profile(mvm, 1, 1);
if (mvm->net_detect && unified_image) { /* If this is a non-unified image, we restart the FW, * so no need to stop the netdetect scan. If that * fails, continue and try to get the wake-up reasons, * but trigger a HW restart by keeping a failure code * in ret.
*/
ret = iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_NETDETECT, false);
}
query_wakeup_reasons:
keep = iwl_mvm_choose_query_wakeup_reasons(mvm, vif, &d3_data); /* has unlocked the mutex, so skip that */ goto out;
err:
mutex_unlock(&mvm->mutex);
out: if (d3_data.status)
kfree(d3_data.status->wake_packet);
kfree(d3_data.status);
iwl_mvm_free_nd(mvm);
/* no need to reset the device in unified images, if successful */ if (unified_image && !ret) { /* nothing else to do if we already sent D0I3_END_CMD */ if (d0i3_first) return 0;
if (!iwl_fw_lookup_notif_ver(mvm->fw, PROT_OFFLOAD_GROUP,
D3_END_NOTIFICATION, 0)) {
ret = iwl_mvm_send_cmd_pdu(mvm, D0I3_END_CMD, 0, 0, NULL); if (!ret) return 0;
} elseif (!(d3_data.d3_end_flags & IWL_D0I3_RESET_REQUIRE)) { return 0;
}
}
/* * Reconfigure the device in one of the following cases: * 1. We are not using a unified image * 2. We are using a unified image but had an error while exiting D3
*/
set_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, &mvm->status);
return 1;
}
int iwl_mvm_resume(struct ieee80211_hw *hw)
{ struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); int ret;
while (true) { /* read pme_ptr if available */ if (mvm->d3_test_pme_ptr) {
pme_asserted = iwl_trans_read_mem32(mvm->trans,
mvm->d3_test_pme_ptr); if (pme_asserted) break;
}
if (msleep_interruptible(100)) break;
if (time_is_before_jiffies(end)) {
IWL_ERR(mvm, "ending pseudo-D3 with timeout after ~60 seconds\n"); return -ETIMEDOUT;
}
}
return 0;
}
staticvoid iwl_mvm_d3_test_disconn_work_iter(void *_data, u8 *mac, struct ieee80211_vif *vif)
{ /* skip the one we keep connection on */ if (_data == vif) return;
if (vif->type == NL80211_IFTYPE_STATION)
ieee80211_connection_loss(vif);
}
iwl_abort_notification_waits(&mvm->notif_wait); if (!unified_image) { int remaining_time = 10;
ieee80211_restart_hw(mvm->hw);
/* wait for restart and disconnect all interfaces */ while (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) &&
remaining_time > 0) {
remaining_time--;
msleep(1000);
}
if (remaining_time == 0)
IWL_ERR(mvm, "Timed out waiting for HW restart!\n");
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.