// 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 #include #include /** * 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 enum rt_status { FW_ALIVE, FW_NEEDS_RESET, FW_ERROR, }; static enum rt_status iwl_mld_check_err_tables(struct iwl_mld *mld, struct ieee80211_vif *vif) { u32 err_id; /* check for lmac1 error */ if (iwl_fwrt_read_err_table(mld->trans, mld->trans->dbg.lmac_error_event_table[0], &err_id)) { if (err_id == RF_KILL_INDICATOR_FOR_WOWLAN && vif) { struct cfg80211_wowlan_wakeup wakeup = { .rfkill_release = true, }; ieee80211_report_wowlan_wakeup(vif, &wakeup, GFP_KERNEL); return FW_NEEDS_RESET; } return FW_ERROR; } /* check if we have lmac2 set and check for error */ if (iwl_fwrt_read_err_table(mld->trans, mld->trans->dbg.lmac_error_event_table[1], NULL)) return FW_ERROR; /* check for umac error */ if (iwl_fwrt_read_err_table(mld->trans, mld->trans->dbg.umac_error_event_table, NULL)) return FW_ERROR; return FW_ALIVE; } static bool iwl_mld_fw_needs_restart(struct iwl_mld *mld, struct ieee80211_vif *vif) { enum rt_status rt_status = iwl_mld_check_err_tables(mld, vif); if (rt_status == FW_ALIVE) return false; if (rt_status == FW_ERROR) { IWL_ERR(mld, "FW Error occurred during suspend\n"); iwl_fwrt_dump_error_logs(&mld->fwrt); iwl_dbg_tlv_time_point(&mld->fwrt, IWL_FW_INI_TIME_POINT_FW_ASSERT, NULL); } return true; } 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_d3_update_mcast_key(struct iwl_mld *mld, struct ieee80211_vif *vif, struct iwl_mld_wowlan_status *wowlan_status, struct ieee80211_key_conf *key, struct iwl_mld_mcast_key_data *key_data) { if (key->keyidx != key_data->id && (key->keyidx < 4 || key->keyidx > 5)) { IWL_ERR(mld, "Unexpected keyId mismatch. Old keyId:%d, New keyId:%d\n", key->keyidx, key_data->id); return; } /* All installed keys are sent by the FW, even weren't * rekeyed during D3. * We remove an existing key if it has the same index as * a new key and a rekey has occurred during d3 */ if (wowlan_status->num_of_gtk_rekeys && key_data->len) { if (key->keyidx == 4 || key->keyidx == 5) { struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); struct iwl_mld_link *mld_link; int link_id = vif->active_links ? __ffs(vif->active_links) : 0; mld_link = iwl_mld_link_dereference_check(mld_vif, link_id); if (WARN_ON(!mld_link)) return; if (mld_link->igtk == key) mld_link->igtk = NULL; mld->num_igtks--; } ieee80211_remove_key(key); return; } iwl_mld_set_key_rx_seq(key, key_data); } 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->num_rx_queues; 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_d3_update_mcast_key(data->mld, vif, wowlan_status, 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; iwl_mld_d3_update_mcast_key(data->mld, vif, wowlan_status, 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_d3_update_mcast_key(data->mld, vif, wowlan_status, key, &wowlan_status->bigtk[status_idx]); } break; default: data->unhandled_cipher = true; return; } data->num_keys++; } static bool 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; 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 true; 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); key_config = ieee80211_gtk_rekey_add(vif, &conf.conf, link_id); if (IS_ERR(key_config)) return false; 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); mld_link->igtk = key_config; mld->num_igtks++; } return true; } static bool 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++) if (!iwl_mld_add_mcast_rekey(vif, key_iter_data->mld, &wowlan_status->gtk[i], link_conf, key_iter_data->gtk_cipher)) return false; if (!iwl_mld_add_mcast_rekey(vif, key_iter_data->mld, &wowlan_status->igtk, link_conf, key_iter_data->igtk_cipher)) return false; for (i = 0; i < ARRAY_SIZE(wowlan_status->bigtk); i++) if (!iwl_mld_add_mcast_rekey(vif, key_iter_data->mld, &wowlan_status->bigtk[i], link_conf, key_iter_data->bigtk_cipher)) return false; return true; } 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; /* 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; } } 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; 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 { mld->trans->system_pm_mode = IWL_PLAT_PM_MODE_D3; 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->trans->system_pm_mode = IWL_PLAT_PM_MODE_DISABLED; mld->fw_status.in_d3 = false; iwl_fw_dbg_read_d3_debug_data(&mld->fwrt); if (iwl_mld_fw_needs_restart(mld, NULL)) ret = -ENODEV; else 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->num_rx_queues; 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 < 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; bool keep_connection; lockdep_assert_wiphy(mld->wiphy); IWL_DEBUG_WOWLAN(mld, "Starting the wowlan resume flow\n"); mld->trans->system_pm_mode = IWL_PLAT_PM_MODE_DISABLED; if (!mld->fw_status.in_d3) { IWL_DEBUG_WOWLAN(mld, "Device_powered_off() was called during wowlan\n"); goto err; } 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); if (iwl_mld_fw_needs_restart(mld, bss_vif)) { fw_err = true; goto err; } resume_data.wowlan_status = kzalloc(sizeof(*resume_data.wowlan_status), GFP_KERNEL); if (!resume_data.wowlan_status) return -1; 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 { 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); } if (!mld->netdetect && !keep_connection) 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: if (resume_data.wowlan_status) { kfree(resume_data.wowlan_status->wake_packet); kfree(resume_data.wowlan_status); } return ret; }