| // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
| /* |
| * Copyright (C) 2022 - 2024 Intel Corporation |
| */ |
| #include <linux/kernel.h> |
| #include <net/mac80211.h> |
| #include "mvm.h" |
| #include "fw/api/context.h" |
| #include "fw/api/datapath.h" |
| |
| static u32 iwl_mvm_get_sec_sta_mask(struct iwl_mvm *mvm, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct ieee80211_key_conf *keyconf) |
| { |
| struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); |
| struct iwl_mvm_vif_link_info *link_info = &mvmvif->deflink; |
| |
| lockdep_assert_held(&mvm->mutex); |
| |
| if (keyconf->link_id >= 0) { |
| link_info = mvmvif->link[keyconf->link_id]; |
| if (!link_info) |
| return 0; |
| } |
| |
| /* AP group keys are per link and should be on the mcast/bcast STA */ |
| if (vif->type == NL80211_IFTYPE_AP && |
| !(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { |
| /* IGTK/BIGTK to bcast STA */ |
| if (keyconf->keyidx >= 4) |
| return BIT(link_info->bcast_sta.sta_id); |
| /* GTK for data to mcast STA */ |
| return BIT(link_info->mcast_sta.sta_id); |
| } |
| |
| /* for client mode use the AP STA also for group keys */ |
| if (!sta && vif->type == NL80211_IFTYPE_STATION) |
| sta = mvmvif->ap_sta; |
| |
| /* During remove the STA was removed and the group keys come later |
| * (which sounds like a bad sequence, but remember that to mac80211 the |
| * group keys have no sta pointer), so we don't have a STA now. |
| * Since this happens for group keys only, just use the link_info as |
| * the group keys are per link; make sure that is the case by checking |
| * we do have a link_id or are not doing MLO. |
| * Of course the same can be done during add as well, but we must do |
| * it during remove, since we don't have the mvmvif->ap_sta pointer. |
| */ |
| if (!sta && (keyconf->link_id >= 0 || !ieee80211_vif_is_mld(vif))) |
| return BIT(link_info->ap_sta_id); |
| |
| /* STA should be non-NULL now, but iwl_mvm_sta_fw_id_mask() checks */ |
| |
| /* pass link_id to filter by it if not -1 (GTK on client) */ |
| return iwl_mvm_sta_fw_id_mask(mvm, sta, keyconf->link_id); |
| } |
| |
| u32 iwl_mvm_get_sec_flags(struct iwl_mvm *mvm, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct ieee80211_key_conf *keyconf) |
| { |
| struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); |
| bool pairwise = keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE; |
| bool igtk = keyconf->keyidx == 4 || keyconf->keyidx == 5; |
| u32 flags = 0; |
| |
| lockdep_assert_held(&mvm->mutex); |
| |
| if (!pairwise) |
| flags |= IWL_SEC_KEY_FLAG_MCAST_KEY; |
| |
| switch (keyconf->cipher) { |
| case WLAN_CIPHER_SUITE_WEP104: |
| flags |= IWL_SEC_KEY_FLAG_KEY_SIZE; |
| fallthrough; |
| case WLAN_CIPHER_SUITE_WEP40: |
| flags |= IWL_SEC_KEY_FLAG_CIPHER_WEP; |
| break; |
| case WLAN_CIPHER_SUITE_TKIP: |
| flags |= IWL_SEC_KEY_FLAG_CIPHER_TKIP; |
| break; |
| case WLAN_CIPHER_SUITE_AES_CMAC: |
| case WLAN_CIPHER_SUITE_CCMP: |
| flags |= IWL_SEC_KEY_FLAG_CIPHER_CCMP; |
| break; |
| case WLAN_CIPHER_SUITE_GCMP_256: |
| case WLAN_CIPHER_SUITE_BIP_GMAC_256: |
| flags |= IWL_SEC_KEY_FLAG_KEY_SIZE; |
| fallthrough; |
| case WLAN_CIPHER_SUITE_GCMP: |
| case WLAN_CIPHER_SUITE_BIP_GMAC_128: |
| flags |= IWL_SEC_KEY_FLAG_CIPHER_GCMP; |
| break; |
| } |
| |
| if (!sta && vif->type == NL80211_IFTYPE_STATION) |
| sta = mvmvif->ap_sta; |
| |
| /* |
| * If we are installing an iGTK (in AP or STA mode), we need to tell |
| * the firmware this key will en/decrypt MGMT frames. |
| * Same goes if we are installing a pairwise key for an MFP station. |
| * In case we're installing a groupwise key (which is not an iGTK), |
| * then, we will not use this key for MGMT frames. |
| */ |
| if ((!IS_ERR_OR_NULL(sta) && sta->mfp && pairwise) || igtk) |
| flags |= IWL_SEC_KEY_FLAG_MFP; |
| |
| if (keyconf->flags & IEEE80211_KEY_FLAG_SPP_AMSDU) |
| flags |= IWL_SEC_KEY_FLAG_SPP_AMSDU; |
| |
| return flags; |
| } |
| |
| struct iwl_mvm_sta_key_update_data { |
| struct ieee80211_sta *sta; |
| u32 old_sta_mask; |
| u32 new_sta_mask; |
| int err; |
| }; |
| |
| static void iwl_mvm_mld_update_sta_key(struct ieee80211_hw *hw, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct ieee80211_key_conf *key, |
| void *_data) |
| { |
| u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, SEC_KEY_CMD); |
| struct iwl_mvm_sta_key_update_data *data = _data; |
| struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); |
| struct iwl_sec_key_cmd cmd = { |
| .action = cpu_to_le32(FW_CTXT_ACTION_MODIFY), |
| .u.modify.old_sta_mask = cpu_to_le32(data->old_sta_mask), |
| .u.modify.new_sta_mask = cpu_to_le32(data->new_sta_mask), |
| .u.modify.key_id = cpu_to_le32(key->keyidx), |
| .u.modify.key_flags = |
| cpu_to_le32(iwl_mvm_get_sec_flags(mvm, vif, sta, key)), |
| }; |
| int err; |
| |
| /* only need to do this for pairwise keys (link_id == -1) */ |
| if (sta != data->sta || key->link_id >= 0) |
| return; |
| |
| err = iwl_mvm_send_cmd_pdu(mvm, cmd_id, CMD_ASYNC, sizeof(cmd), &cmd); |
| |
| if (err) |
| data->err = err; |
| } |
| |
| int iwl_mvm_mld_update_sta_keys(struct iwl_mvm *mvm, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| u32 old_sta_mask, |
| u32 new_sta_mask) |
| { |
| struct iwl_mvm_sta_key_update_data data = { |
| .sta = sta, |
| .old_sta_mask = old_sta_mask, |
| .new_sta_mask = new_sta_mask, |
| }; |
| |
| ieee80211_iter_keys_rcu(mvm->hw, vif, iwl_mvm_mld_update_sta_key, |
| &data); |
| return data.err; |
| } |
| |
| static int __iwl_mvm_sec_key_del(struct iwl_mvm *mvm, u32 sta_mask, |
| u32 key_flags, u32 keyidx, u32 flags) |
| { |
| u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, SEC_KEY_CMD); |
| struct iwl_sec_key_cmd cmd = { |
| .action = cpu_to_le32(FW_CTXT_ACTION_REMOVE), |
| .u.remove.sta_mask = cpu_to_le32(sta_mask), |
| .u.remove.key_id = cpu_to_le32(keyidx), |
| .u.remove.key_flags = cpu_to_le32(key_flags), |
| }; |
| |
| return iwl_mvm_send_cmd_pdu(mvm, cmd_id, flags, sizeof(cmd), &cmd); |
| } |
| |
| int iwl_mvm_mld_send_key(struct iwl_mvm *mvm, u32 sta_mask, u32 key_flags, |
| struct ieee80211_key_conf *keyconf) |
| { |
| u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, SEC_KEY_CMD); |
| struct iwl_sec_key_cmd cmd = { |
| .action = cpu_to_le32(FW_CTXT_ACTION_ADD), |
| .u.add.sta_mask = cpu_to_le32(sta_mask), |
| .u.add.key_id = cpu_to_le32(keyconf->keyidx), |
| .u.add.key_flags = cpu_to_le32(key_flags), |
| .u.add.tx_seq = cpu_to_le64(atomic64_read(&keyconf->tx_pn)), |
| }; |
| int max_key_len = sizeof(cmd.u.add.key); |
| int ret; |
| |
| if (keyconf->cipher == WLAN_CIPHER_SUITE_WEP40 || |
| keyconf->cipher == WLAN_CIPHER_SUITE_WEP104) |
| max_key_len -= IWL_SEC_WEP_KEY_OFFSET; |
| |
| if (WARN_ON(keyconf->keylen > max_key_len)) |
| return -EINVAL; |
| |
| if (WARN_ON(!sta_mask)) |
| return -EINVAL; |
| |
| if (keyconf->cipher == WLAN_CIPHER_SUITE_WEP40 || |
| keyconf->cipher == WLAN_CIPHER_SUITE_WEP104) |
| memcpy(cmd.u.add.key + IWL_SEC_WEP_KEY_OFFSET, keyconf->key, |
| keyconf->keylen); |
| else |
| memcpy(cmd.u.add.key, keyconf->key, keyconf->keylen); |
| |
| if (keyconf->cipher == WLAN_CIPHER_SUITE_TKIP) { |
| memcpy(cmd.u.add.tkip_mic_rx_key, |
| keyconf->key + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY, |
| 8); |
| memcpy(cmd.u.add.tkip_mic_tx_key, |
| keyconf->key + NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY, |
| 8); |
| } |
| |
| ret = iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, sizeof(cmd), &cmd); |
| if (ret) |
| return ret; |
| |
| /* |
| * For WEP, the same key is used for multicast and unicast so need to |
| * upload it again. If this fails, remove the original as well. |
| */ |
| if (keyconf->cipher == WLAN_CIPHER_SUITE_WEP40 || |
| keyconf->cipher == WLAN_CIPHER_SUITE_WEP104) { |
| cmd.u.add.key_flags ^= cpu_to_le32(IWL_SEC_KEY_FLAG_MCAST_KEY); |
| ret = iwl_mvm_send_cmd_pdu(mvm, cmd_id, 0, sizeof(cmd), &cmd); |
| if (ret) |
| __iwl_mvm_sec_key_del(mvm, sta_mask, key_flags, |
| keyconf->keyidx, 0); |
| } |
| |
| return ret; |
| } |
| |
| int iwl_mvm_sec_key_add(struct iwl_mvm *mvm, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct ieee80211_key_conf *keyconf) |
| { |
| u32 sta_mask = iwl_mvm_get_sec_sta_mask(mvm, vif, sta, keyconf); |
| u32 key_flags = iwl_mvm_get_sec_flags(mvm, vif, sta, keyconf); |
| struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); |
| struct iwl_mvm_vif_link_info *mvm_link = NULL; |
| int ret; |
| |
| if (keyconf->keyidx == 4 || keyconf->keyidx == 5) { |
| unsigned int link_id = 0; |
| |
| /* set to -1 for non-MLO right now */ |
| if (keyconf->link_id >= 0) |
| link_id = keyconf->link_id; |
| |
| mvm_link = mvmvif->link[link_id]; |
| if (WARN_ON(!mvm_link)) |
| return -EINVAL; |
| |
| if (mvm_link->igtk) { |
| IWL_DEBUG_MAC80211(mvm, "remove old IGTK %d\n", |
| mvm_link->igtk->keyidx); |
| ret = iwl_mvm_sec_key_del(mvm, vif, sta, |
| mvm_link->igtk); |
| if (ret) |
| IWL_ERR(mvm, |
| "failed to remove old IGTK (ret=%d)\n", |
| ret); |
| } |
| |
| WARN_ON(mvm_link->igtk); |
| } |
| |
| ret = iwl_mvm_mld_send_key(mvm, sta_mask, key_flags, keyconf); |
| if (ret) |
| return ret; |
| |
| if (mvm_link) |
| mvm_link->igtk = keyconf; |
| |
| /* We don't really need this, but need it to be not invalid, |
| * and if we switch links multiple times it might go to be |
| * invalid when removed. |
| */ |
| keyconf->hw_key_idx = 0; |
| |
| return 0; |
| } |
| |
| static int _iwl_mvm_sec_key_del(struct iwl_mvm *mvm, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct ieee80211_key_conf *keyconf, |
| u32 flags) |
| { |
| u32 sta_mask = iwl_mvm_get_sec_sta_mask(mvm, vif, sta, keyconf); |
| u32 key_flags = iwl_mvm_get_sec_flags(mvm, vif, sta, keyconf); |
| struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); |
| int ret; |
| |
| if (WARN_ON(!sta_mask)) |
| return -EINVAL; |
| |
| if (keyconf->keyidx == 4 || keyconf->keyidx == 5) { |
| struct iwl_mvm_vif_link_info *mvm_link; |
| unsigned int link_id = 0; |
| |
| /* set to -1 for non-MLO right now */ |
| if (keyconf->link_id >= 0) |
| link_id = keyconf->link_id; |
| |
| mvm_link = mvmvif->link[link_id]; |
| if (WARN_ON(!mvm_link)) |
| return -EINVAL; |
| |
| if (mvm_link->igtk == keyconf) { |
| /* no longer in HW - mark for later */ |
| mvm_link->igtk->hw_key_idx = STA_KEY_IDX_INVALID; |
| mvm_link->igtk = NULL; |
| } |
| } |
| |
| ret = __iwl_mvm_sec_key_del(mvm, sta_mask, key_flags, keyconf->keyidx, |
| flags); |
| if (ret) |
| return ret; |
| |
| /* For WEP, delete the key again as unicast */ |
| if (keyconf->cipher == WLAN_CIPHER_SUITE_WEP40 || |
| keyconf->cipher == WLAN_CIPHER_SUITE_WEP104) { |
| key_flags ^= IWL_SEC_KEY_FLAG_MCAST_KEY; |
| ret = __iwl_mvm_sec_key_del(mvm, sta_mask, key_flags, |
| keyconf->keyidx, flags); |
| } |
| |
| return ret; |
| } |
| |
| int iwl_mvm_sec_key_del_pasn(struct iwl_mvm *mvm, |
| struct ieee80211_vif *vif, |
| u32 sta_mask, |
| struct ieee80211_key_conf *keyconf) |
| { |
| u32 key_flags = iwl_mvm_get_sec_flags(mvm, vif, NULL, keyconf) | |
| IWL_SEC_KEY_FLAG_MFP; |
| |
| if (WARN_ON(!sta_mask)) |
| return -EINVAL; |
| |
| return __iwl_mvm_sec_key_del(mvm, sta_mask, key_flags, keyconf->keyidx, |
| 0); |
| } |
| |
| int iwl_mvm_sec_key_del(struct iwl_mvm *mvm, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct ieee80211_key_conf *keyconf) |
| { |
| return _iwl_mvm_sec_key_del(mvm, vif, sta, keyconf, 0); |
| } |
| |
| static void iwl_mvm_sec_key_remove_ap_iter(struct ieee80211_hw *hw, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct ieee80211_key_conf *key, |
| void *data) |
| { |
| struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); |
| unsigned int link_id = (uintptr_t)data; |
| |
| if (key->hw_key_idx == STA_KEY_IDX_INVALID) |
| return; |
| |
| if (sta) |
| return; |
| |
| if (key->link_id >= 0 && key->link_id != link_id) |
| return; |
| |
| _iwl_mvm_sec_key_del(mvm, vif, NULL, key, CMD_ASYNC); |
| key->hw_key_idx = STA_KEY_IDX_INVALID; |
| } |
| |
| void iwl_mvm_sec_key_remove_ap(struct iwl_mvm *mvm, |
| struct ieee80211_vif *vif, |
| struct iwl_mvm_vif_link_info *link, |
| unsigned int link_id) |
| { |
| u32 sec_key_id = WIDE_ID(DATA_PATH_GROUP, SEC_KEY_CMD); |
| u8 sec_key_ver = iwl_fw_lookup_cmd_ver(mvm->fw, sec_key_id, 0); |
| |
| if (WARN_ON_ONCE(vif->type != NL80211_IFTYPE_STATION || |
| link->ap_sta_id == IWL_MVM_INVALID_STA)) |
| return; |
| |
| if (!sec_key_ver) |
| return; |
| |
| ieee80211_iter_keys_rcu(mvm->hw, vif, |
| iwl_mvm_sec_key_remove_ap_iter, |
| (void *)(uintptr_t)link_id); |
| } |